初めに 最近は社内の活動を少しでもやりやすくするための社内システム開発を龍ちゃんです。合わせて、AIと協業して開発する検証も進めています。AIを開発に入れてから、少ない人数で開発を回せているので素晴らしいですね。今回は、AIとはちょっと関係ない領域でのコンテンツですけど… 今回は、GitHubの優れた編集・レビュー機能をそのまま活用しながら、APIで簡単に自分のシステムに組み込む方法を紹介します。 なぜGitHubをCMSとして使うのか? 最大の魅力:GitHubの機能をフル活用できる 編集 : VS Codeライクなエディタで快適に編集 レビュー : Pull Requestで複数人でのレビュー 履歴管理 : すべての変更履歴が自動保存 検索 : リポジトリ内の強力な検索機能 権限管理 : チームでの細かいアクセス制御 つまり、 コンテンツの作成・編集・管理はGitHubに任せて、自分のシステムは「読み取り」に専念できる のです。 こんな使い方ができる GitHubリポジトリ構成例: /posts ├── 2024-01-15-github-api-guide.md ├── 2024-01-20-nestjs-tips.md └── 2024-01-25-typescript-tricks.md → これをAPIで取得して、ブログや社内ドキュメントサイトに表示! こちらのシステムと統合して使用する ことで、GitHub上で作成した成果物ををシステムに統合することができます。 Personal Access Token (PAT) の設定 GitHub APIを使うためには、PATの設定が必要です。 基本設定(検証で使いまわししたい場合) GitHub Settings → Developer settings → Personal access tokens → Tokens (classic) Generate new token をクリック 権限は repo を選択(プライベートリポジトリアクセス用) セキュリティ重視の場合 Fine-grained PAT の使用を推奨します: 対象リポジトリを限定可能 必要な権限: Contents: Read のみでOK 有効期限の強制設定でより安全 レート制限が劇的に改善されるんです! PAT認証の違いは劇的 なんですよ。 認証方式 1時間あたりの制限 1分あたり換算 認証なし 60回 1回/分 PAT認証 5,000回 83回/分 *つまり、PATを使うだけで83倍のリクエストが可能になるんです!**これは使わない手はないですよね。 コピペで動く!NestJSコントローラー実装 環境変数を設定 GITHUB_OWNER=your-username GITHUB_REPO=your-repo-name GITHUB_TARGET_DIRECTORY=/posts GITHUB_PAT=ghp_xxxxxxxxxxxxxxxxxxxx コントローラーにコピペするだけ! import { Controller, Get, Param, HttpException, HttpStatus } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; @ApiTags('GitHub CMS') @Controller('api/github-cms') export class GitHubCmsController { private readonly baseUrl = 'https://api.github.com'; private readonly githubPat = '取得したPATを埋め込んでね!'; private readonly githubOwner = 'リポジトリOwnerを入れてね'; private readonly githubRepo = 'リポジトリ名を入れてね!'; private readonly githubTargetDirectory = '/posts'; private async githubRequest(path: string): Promise<any> { const response = await fetch(`${this.baseUrl}${path}`, { headers: { 'Authorization': `Bearer ${this.githubPat}`, 'Accept': 'application/vnd.github+json', 'User-Agent': 'github-cms-client/1.0' } }); if (!response.ok) { const errorMessages = { 401: 'GitHub認証エラー: PATを確認してください', 403: 'アクセス権限がありません', 404: 'リソースが見つかりません', 429: 'レート制限に達しました' }; throw new HttpException( errorMessages[response.status] || `GitHub API Error: ${response.status}`, response.status ); } return response.json(); } @Get('files') @ApiOperation({ summary: 'Markdownファイル一覧を取得' }) @ApiResponse({ status: 200, description: 'ファイル一覧' }) async getFiles() { const path = `/repos/${this.githubOwner}/${this.githubRepo}/contents${this.githubTargetDirectory}`; const files = await this.githubRequest(path); const markdownFiles = files.filter((file: any) => file.type === 'file' && file.name.endsWith('.md') ); return markdownFiles.map((file: any) => ({ name: file.name, path: file.path, sha: file.sha, size: file.size, downloadUrl: file.download_url })); } @Get('files/:filename') @ApiOperation({ summary: '特定のMarkdownファイルの内容を取得' }) @ApiResponse({ status: 200, description: 'ファイル内容' }) @ApiResponse({ status: 404, description: 'ファイルが見つかりません' }) async getFileContent(@Param('filename') filename: string) { if (!filename.endsWith('.md')) { filename += '.md'; } const path = `/repos/${this.githubOwner}/${this.githubRepo}/contents${this.githubTargetDirectory}/${filename}`; try { const file = await this.githubRequest(path); const content = Buffer.from(file.content, 'base64').toString('utf-8'); return { name: file.name, path: file.path, content: content, sha: file.sha, size: file.size, downloadUrl: file.download_url, htmlUrl: file.html_url }; } catch (error) { if (error.status === 404) { throw new HttpException('ファイルが見つかりません', HttpStatus.NOT_FOUND); } throw error; } } @Get('rate-limit') @ApiOperation({ summary: 'GitHub APIレート制限情報を取得' }) @ApiResponse({ status: 200, description: 'レート制限情報' }) async getRateLimit() { const data = await this.githubRequest('/rate_limit'); return { limit: data.rate.limit, remaining: data.rate.remaining, resetAt: new Date(data.rate.reset * 1000), used: data.rate.limit - data.rate.remaining }; } } 使い方はこんな感じ! APIエンドポイント # ファイル一覧を取得 GET /api/github-cms/files # 特定のファイル内容を取得 GET /api/github-cms/files/2024-01-15-github-api-guide.md # レート制限を確認 GET /api/github-cms/rate-limit レスポンス例 // GET /api/github-cms/files [ { "name": "2024-01-15-github-api-guide.md", "path": "posts/2024-01-15-github-api-guide.md", "sha": "abc123...", "size": 2048, "downloadUrl": "https://raw.githubusercontent.com/..." }, // ... ] // GET /api/github-cms/files/2024-01-15-github-api-guide.md { "name": "2024-01-15-github-api-guide.md", "path": "posts/2024-01-15-github-api-guide.md", "content": "# GitHub API活用ガイド\\n\\n本文...", "sha": "abc123...", "size": 2048, "downloadUrl": "https://raw.githubusercontent.com/...", "htmlUrl": "https://github.com/..." } フロントエンドでの使用例 // React/Next.jsでの実装 import { useEffect, useState } from 'react'; export default function BlogList() { const [posts, setPosts] = useState([]); useEffect(() => { fetch('/api/github-cms/files') .then(res => res.json()) .then(data => setPosts(data)); }, []); return ( <div> <h1>ブログ記事一覧</h1> {posts.map(post => ( <div key={post.sha}> <Link href={`/blog/${post.name.replace('.md', '')}`}> {post.name.replace('.md', '')} </Link> </div> ))} </div> ); } まとめ GitHubをCMSとして使うメリット: 編集・レビュー機能が無料で使える – GitHubの優れたUIをそのまま活用 APIアクセスが簡単 – シンプルな実装で十分実用的 レート制限もPATで解決 – 83倍のリクエストが可能に バージョン管理付き – すべての変更履歴が自動保存 つまり、 コンテンツ管理の面倒な部分はGitHubに任せて、自分はAPIで読み取るだけ という理想的な構成が実現できます。 こんなプロジェクトにおすすめ 個人ブログ・技術ブログ 社内ドキュメントサイト 静的サイトのコンテンツ管理 チームで編集する設定ファイル管理 GitHubの編集・レビュー機能を活用しながら、APIで簡単に自分のシステムに組み込める。これが「GitHub as a CMS」の魅力です! 参考資料 GitHub公式ドキュメント GitHub REST API Documentation – APIの完全なリファレンス Rate limits for the REST API – レート制限の詳細(認証なし60回/時 vs PAT認証5,000回/時の比較表あり) Contents API – ファイル取得APIの仕様 Personal Access Token (PAT) について Creating a personal access token – PATの作成手順 Scopes for OAuth apps – 必要な権限スコープの説明 実装に役立つリソース Octokit.js – GitHub公式のJavaScript SDKもあります(今回は使わずにfetch APIで実装) gray-matter – Markdown Frontmatterのパース用ライブラリ ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post GitHubをCMSに!NestJSでAPI実装する実践方法【コード付き】 first appeared on SIOS Tech. Lab .