本記事は リンクアンドモチベーション Advent Calendar 2023 の7日目です🎄
こんにちは! リンクアンドモチベーションの川津です🌟
今日のWebサービス開発をしている現場では OpenAPI Specification (旧Swagger) を使って API 仕様を書いてる所は多いかと思います。
弊社でも使ってまして、先日 API 仕様のレビュー依頼が来たのですが、見るまでの手順 ↓ が煩雑で「単に URL 共有してくれると嬉しいのにな〜」と思ったのが本記事の発端です😅
- レビュー対象の Branch を pull する
- VSCode で Specification ファイル (.yaml / .json) を開く
- OpenAPI (Swagger) Editor でプレビュー表示する
完成品
という事でツールを作ってみました。
以下の例では OAI 公式の GitHub で公開されているサンプル petstore.yaml を SwaggerUI を用いて描画しています。
GitHub repository:
このツールそのものは認証等のセキュリティ対策は無いので、基本的に利用の際は インターネットに公開してはいけません!
プライベートネットワーク中にホストするか、別途IPアドレス制限等をします。
使い方
URL
URL で GitHub 上の OpenAPI Specification ファイル (.yaml
/ .json
) を指定できます。
https://swagger-github.vercel.app/{owner}/{repo}/{branch}/{path_to_spec}
この URL のパス部分は GitHub Raw ファイル URL raw.githubusercontent.com と同じです。
GitHub (Private Repo) 認証
サーバーサイドの環境変数もしくは .env
ファイルで GitHub トークンを指定する様にしました。
# Your Authorization Bearer token. (e.g. GitHub "personal access token")
BEARER_TOKEN=
前述のサンプルで公開しているケースでは
BEARER_TOKEN
は未指定にしています。
仕組み
要素技術
- Node.js → 20.10.0
- Next.js → 単に楽なので使いました (FE/BEまとめて書ける & デプロイ環境)
- デプロイ先
- Vercel → 楽だけど、課金しないと IP 制限できなさそう
- AWS Amplify → CloudFront (WAF) もつければ IP 制限できる
シーケンス
特別な事は何もなく、良く思いつくであろう構成です。
- Top-Level Navigation アクセスでは、SwaggerUI 等のシングルページを返す
- URLパスを元に、SwaggerUI が OpenAPI Spec ファイル (
.yaml
/.json
) を Ajax で要求 - (セキュリティ認証の為に) GitHub へのアクセスをサーバーがプロキシして結果を返す
ページ/API
Next.js なので React でフロントコードを書きます。
SwaggerUI 自体は swagger-ui-react Package を使って数行で描画できます。
'use client'
import dynamic from 'next/dynamic'
const SwaggerUI = dynamic(() => import('swagger-ui-react'), { ssr: false })
import 'swagger-ui-react/swagger-ui.css'
export default function Home({ params }: { params: { path: string[] } }) {
const githubPath = params.path.join('/')
return (
<main className="">
<SwaggerUI url={`/api/github/${githubPath}`} deepLinking={true} />
</main>
)
}
↑ で url={"..."}
指定した URL に Ajax リクエストが来ます。
Next.js の Route Handler で OpenAPI Spec (.yaml
/ .json
) を返す API を用意します。
export async function GET(
request: Request,
{ params }: { params: { path: string[] } },
) {
// 認証が必要なら、環境変数から取って来る。
const token = process.env.BEARER_TOKEN
const options = token ? {
headers: { Authorization: `Bearer ${token}` },
} : undefined
// raw.githubusercontent.com にアクセス.
const githubPath = params.path.join('/')
const url = new URL(githubPath, 'https://raw.githubusercontent.com')
const res = await fetch(url, options)
// ...(省略)...
return new Response(res.blob(), { status: res.status })
}
実際、書いたコードはこれだけです😳
※一部、力技で実装した箇所が...
OpenAPI Specification では $ref
参照を使って、ファイルを分割管理する事ができます。
paths:
/oauth2/token:
post:
$ref: "./child.yml"
...
operationId: postOAuth2
tags:
- OAuth2
summary: アクセストークンを...
requestBody:
content:
...
しかし SwaggerUI を使って表示すると、本来のカテゴリ箇所 (tag) ではなく、全て default
カテゴリに表示されてしまいました。
これはどうやら、子供のファイル child.yaml
の方に tags を記述している事が原因のようです。
SwaggerUI はカテゴリ POST /oauth2/token
をクリックして開閉するまで、子供のファイル child.yaml
を取得しにいかない為、中身がまだ分からないのですね。
GPT に「どうしたらいいの?」と聞くと「サーバーサイドで全部くっつけたらいいよ」とかなり力技の提案をされたので、その通りにしてみました...😅
デプロイ
デプロイ先は次の3パターンを考えました。
重視した観点は ①「なるべく保守をしたくない」、②「インターネットに公開しない」です。
- プライベートネットワーク上のサーバー (EC2 とか)
- Vercel
- AWS Amplify + CloudFront(WAF)
1. プライベートネットワーク上のサーバー (EC2 とか)
ネットワーク上、外部からアクセスできないので正直イチオシの方法です...笑
どこかのサーバー (野良EC2とか) に入って、Next.js サーバーを起動するだけです。
npm run build
npm run start
ブラウザで http://{server-ip-address}:3000 URL にアクセスします。
2. Vercel
Next.js で書いたのなら、Vercel が最も手っ取り早いデプロイオプションでしょう。
画面の指示に従うだけで簡単にできちゃうので、手順は説明しません。
しかしながら、残念な事に「IPアドレス制限」(Trusted IPs) は Enterprise プランでないと使えないようです。
一応アプリケーションコードで弾く方法もありますが、少し怖いので不採用としました。
3. AWS Amplify + CloudFront(WAF)
この方法は正直、初期構築は一番手間なのですが、その後の保守はわりと楽なので社内にデプロイしたのはこの方法を採用しました。
- Next.js コードは AWS Amplify でサーバレスデプロイできます
- 更に CloudFront (WAF) を使うことで、IPアドレス制限ができる
CloudFront(WAF) → AWS Amplify 間は Basic 認証はあれど、実際にはインターネット通信なので、唯一そこが心配...
AWS公式様のこの記事 ↓ が大変わかりやすいので、本記事では手順の説明は割愛します。
一つハマったのは AWS Amplify で実行時に「環境変数が適用されない」問題です。
AWS Console (Amplify) で環境変数を設定しても何故か反映されません。
理由は公式のページ ↓ に書いてありました。どうやら実行時の環境変数は .env
ファイルで指定する必要があるみたいです。
最終的に AWS Amplify の Build settings amplify.yml
はこうなります。
version: 1
frontend:
phases:
preBuild:
commands:
- nvm use 18
- node -v
- npm ci
build:
commands:
- echo "BEARER_TOKEN=$BEARER_TOKEN" >> .env
- echo "PROXY_DOMAIN=$PROXY_DOMAIN" >> .env
- echo "RESOLVE_ALL_REFS=$RESOLVE_ALL_REFS" >> .env
- echo "RESOLVE_ALL_REFS_DEPTH_LIMIT=$RESOLVE_ALL_REFS_DEPTH_LIMIT" >> .env
- npm run build
artifacts:
baseDirectory: .next
files:
- '**/*'
cache:
paths:
本当は node 20 にしたいけど、現状 Amplify では 18 までしか使えなそう。
最後に
ここまで読んで下さってありがとうございます。
結構ニッチな記事なのですが、どの現場でも一律「これ使えば解決するよね?」という策があまりなさそうだったので、記事化してみました。
この記事が、もし同じ事で悩んでおられる方の役に立てれば幸いです。