Next.jsのpagesルーターでは、APIルートが準備されている。pagesフォルダの配下にapiというフォルダを作り、その中にAPIを作ることができた。
Appルーターでは少し作り方が変わっている。
以下はAppルーターのルートハンドラーについての公式ページ。
Appルーターでの設定方法と、簡単なテスト用のAPIをメモしておく。
あくまでテスト用なので、本番で使うことは想定していない。
Next.js Appルーターのroute.ts
作り方はすごく簡単で、フォルダ内にroute.ts
を作るだけ。
例えばtest-apiフォルダにroute.tsを作ると、.com/test-api
にアクセスすることでAPIとして使える。
以下はとても簡単なテスト用のAPI。
アクセスすると、ランダムな数字をjson形式で返す。
//test-api/route.ts import { NextResponse } from "next/server"; export async function GET() { console.log(">>> Called /test-api "); return NextResponse.json({ data: Math.random() }); }
上の例のMath.random()
のところに、返したいjsonの内容をべた書き(変数でもよい)すれば、いろんなデータを返すことができる。
応用としてはtsファイルをインポートして、json形式で返したり、ローカルファイルを開いてデータを返したり、とかができる。
以下にローカルファイルを開く場合の例を記載する。
本番環境ではそんなことはしないだろうが、ローカルテストを想定し、テスト用のjsonファイルを準備しておいてそれを呼び出す形で作る。
開発中のサイトからfetchを使って、ローカルホストでnext devしているAPIを呼べばテストできる。
jsonファイルを開いて返すAPI
テスト用のデータを返すAPI。
今回はjsonを使っているが、少し修正すればcsvなど他のファイルでも対応できる。
import { NextResponse } from "next/server"; import fs from "fs"; import path from "path"; export async function GET() { const postsDirectory = path.join(process.cwd(), "test"); const fullPath = path.join(postsDirectory, `test-data.json`); const fileContents = fs.readFileSync(fullPath, "utf-8"); const data = JSON.parse(fileContents); console.log(`>>> Called test-data api); return NextResponse.json(data); }
node.jsのファイル読み込みを使ってjsonファイルを読み込み、それをjson形式にして返すだけのAPI。
まずファイルがある場所のパスを取得するため、実行時のカレントディレクトリをprocess.cwd()で取得し、その配下のtestフォルダを指定するためpathを連結している。
postsDirectoryで作ったパスにファイル名をくっつけて、readFileSyncでファイルを読み込む。
fs.readFileSyncは、同期処理になる。要はファイルを開き終わってから後続の処理をスタートするということ。
あとは開いたファイルをjson形式にして、それを返すだけ。
多分本番のサーバーでこんな使い方はしないだろうけど、ローカルでテスト用に使うにはとても便利。
次はDynamic Routesを使ったAPIの例。
Dynamic Routesを使ってjsonを返す
やっていることは変わらないが、まず[]で囲ったフォルダを作り、名前を[id]としておく。
例えばtest-dynamic/a
でアクセスがあった場合、idはaになる。
このaを取得してあれこれするのがDynamic Routesを使ったAPI。
idに対応するファイル名のjsonを用意しておいて、ファイル名を生成するときに、取得したidを使う。
ちなみに存在しないidの場合、ステータスコードは500、「このページは動作していません」とテキストが出る。本番で使うことは想定していないので、対応する処理は入れてない。
// test-dynamic/[id]/route.ts import { NextResponse } from "next/server"; import fs from "fs"; import path from "path"; export async function GET( request: Request, { params }: { params: { id: string } }, ) {{ const postsDirectory = path.join(process.cwd(), "test"); const id = params.id; const fullPath = path.join(postsDirectory, `${id}.json`); const fileContents = fs.readFileSync(fullPath, "utf-8"); const data = JSON.parse(fileContents); console.log(`>>> Called test-data api ${id}`); return NextResponse.json(data); }
開発環境とvercel本番環境の違い
next devで動く開発サーバーは、静的で作っていてもSSR的に動く。
そのため、vercelなど本番環境に上げる場合、ビルドした時にデータはキャッシュされる。
例えば、該当のurlにアクセスしたときの時間を返すAPIを静的にビルドしてvercelに上げると、ビルドしたときの時間を永遠に返すAPIになる。
そういう理由から、SSR的な挙動をさせたいAPIをvercelに上げてテストに使いたい、なんて時は注意が必要になる。
これの対処方法は、ファイルの中に、キャッシュしないでねと自分で記述すること。
Next.jsでは明示的に示さない限り、ビルドすると静的なサイトというか、データは全てキャッシュされる。
キャッシュしたくないところは、自身で明示する必要がある。
それが以下のexport const dynamic = "force-dynamic";
のところ。
import ja from "date-fns/locale/ja"; import { format } from "date-fns"; import { NextResponse } from "next/server"; export const dynamic = "force-dynamic"; export async function GET() { const now = new Date( Date.now() + (new Date().getTimezoneOffset() + 9 * 60) * 60 * 1000, ); const nowJa = format(now, "yyyy年M月d日(E) HH:mm:ss", { locale: ja }); console.log(">>> Called api アクセスした日付を返すAPI"); console.log(nowJa); return NextResponse.json({ data: nowJa }); }
dynamicの値を変えることで、キャッシュの動きを制御できる。
何も指定しない場合は基本的には全てキャッシュされる(一部例外あり)。
上の例のように、force-dynamicにすると、このAPIはキャッシュされずに、アクセスがあると再検証が走る。pageルーターのgetServerSidePropsと同じ動きになる。
詳細は以下で。
next devで使う分には問題ないし、普通はこれをvercelに上げるなんてことはないと思うけど。
vercel自体の動きをテスト確認したいとか、特殊な状況でテスト用のAPIを本番でも使いたいという時に、キャッシュの指定をしないと動かないよ、というだけの話。
まとめ
本番のAPIがまだできてないとか、APIへのアクセスが従量課金とか、様々な理由でAPIに接続できないけど、データ取得の部分は作っておきたいし、本番を想定したデータでデザイン確認したい、なんて時にNext.jsのAPI機能はとても便利。
Appルーターで新しくなったfetchでローカルのテストAPIにつなげておいて、本番APIが使える段階になったら、接続先を変えてテストをすればよい、というのは楽。
pageルーターの時のようにフォルダ名の縛りがないので、そこかしこに適当にroute.tsを作ってしまって管理が大変になりそうだなとは思う。