Next.js の Web API 機能
Next.js では、pages/api
ディレクトリ以下に TypeScript (JavaScript) コードを配置するだけで、クライアントサイド JavaScript から呼び出せる API を定義することができます。
例えば、次のようなファイルを作成します。
import type { NextApiRequest, NextApiResponse } from 'next'
type Response = {
name: string
}
export default (req: NextApiRequest, res: NextApiResponse<Response>) => {
res.status(200).json({ name: 'John Doe' })
// チェーン呼び出しせずに次のように記述しても OK
// res.statusCode = 200
// res.json({ name: 'John Doe'})
}
あとは、Next.js サーバーを起動した状態で、/api/hello
というエンドポイントにアクセスすると、次のような JSON データを取得できます。
{"name":"John Doe"}
API 機能は次のような用途に使用することができます。
- フォームに入力された値が POST されたときにサーバーサイドで DB に保存する
- 3rd パーティ製の Web API の呼び出しを中継する
このような機能を実装するには、データベースのパスワードや、3rd パーティ製 Web API のアクセスキーなどが必要になりますが、そういった情報は Next.js サーバ側の環境変数などに保存しておくことができます。
そうすれば、API の実装コードから process.env.XXX_ACCESS_KEY
のように参照できます。
pages/api
ディレクトリ以下の実装内容が、クライアントに見られてしまうことはありません。
API のコードは Next.js サーバー上で実行されるため、この API 機能を使用するには、Web サイトのホスティング時に Next.js サーバー (next start
) が必要です。
必然的に、Vercel のサービス などを使ってホスティングすることになるため、静的サーバー用の HTML ファイル群を生成する next exports
コマンドは実行できなくなります(pages/api
以下にファイルを作成すると、next build
までしか成功しなくなります)。
クエリパラメーターに対応する
例えば、ゲームの情報を取得する API として /api/games
というエンドポイントを定義するとします。
パラメーターとして 1
などのゲーム ID を指定する場合、次のような 2 通りの指定方法が考えられます。
/api/games/1
(REST 形式の URL にする)/api/games?id=1
(クエリ文字列を付加する)
以下、それぞれの実装方法を説明します。
REST 形式
/api/games/1
という REST API 風の URL でアクセスしたいときは、通常のページコンポーネントと同様のダイナミックルーティングの機能を使って API を実装します。
例えば、/api/games/1
や /api/games/2
のような URL をハンドルするには、pages/api/games/[id].ts
というファイルを作成します。
id
部分に指定されたパラメーターの値は、ハンドラー関数に渡される NextApiRequest
オブジェクトを使って、req.query.id
のように参照することができます。
import type { NextApiRequest, NextApiResponse } from 'next'
export type Game = {
id: string
title: string
genre: string
}
// API のレスポンス型
export type GamesApiResponse = {
game?: Game
debugMessage?: string
}
// API のエントリポイント
export default function gamesApi(
req: NextApiRequest,
res: NextApiResponse<GamesApiResponse>
): void {
const id = req.query.id as string
const game = fetchGameData(id)
if (game) {
res.status(200).json({ game })
} else {
res.status(400).json({ debugMessage: `Game [id=${id}] not found` })
}
}
// 擬似的なデータフェッチ関数
function fetchGameData(id: string): Game | undefined {
const games: Game[] = [
{ id: '1', title: 'ドンキーコング', genre: 'アクション' },
{ id: '2', title: 'ゼビウス', genre: 'シューティング' },
{ id: '3', title: 'ロードランナー', genre: 'パズル' },
]
return games.find((game) => game.id === id)
}
例えば、/api/games/3
というアドレスでアクセスすると、次のような JSON データが返されます。
{"game":{"id":"3","title":"ロードランナー","genre":"パズル"}}
クエリ文字列形式
/api/games?id=1
といったクエリ文字列の形で指定されたパラメータ (?id=1
) を取得するには、API を実装するファイルを、pages/api/games.ts
あるいは pages/api/games/index.ts
という名前で作成します。
パラメーターの参照方法は前述の方法と同じで、req.query.id
のように参照できます。
// ...実装方法はまったく同じ...
export default function gamesApi(
req: NextApiRequest,
res: NextApiResponse<GamesApiResponse>
): void {
const id = req.query.id as string
const game = fetchGameData(id)
if (game) {
res.status(200).json({ game })
} else {
res.status(400).json({ debugMessage: `Game [id=${id}] not found` })
}
}
例えば、/api/games?id=2
というアドレスでアクセスすると、次のような JSON データが返されます。
{"game":{"id":"2","title":"ゼビウス","genre":"シューティング"}}
どちらの形式を使うべきか?
どちらでもよいですが、パラメーターが 1 つの場合は REST 形式 (pages/api/games/[id].ts
) で定義するとシンプルです。
API を呼び出すときに、いちいち id
のようなパラメーター名を指定する必要がありません(呼び出し例: /api/games/1
)。
逆にパラメーターを複数指定する可能性があって、その指定順序に制約がない場合は、クエリ文字列を使った形式 (pages/api/games.tsx
) で定義するのがよいと思います。
呼び出し時にキー&バリューの形でパラメーターを指定するので、間違った値を指定してしまうミスが減ります(呼び出し例: /api/games?genre=ACT&year=1990
)。
React コンポーネントから API を呼び出す
上記のように定義した API を React コンポーネントの実装から呼び出すには、useSWR
フックを使用するのが簡単です。
このフックは Vercel が swr
パッケージとして提供しています。
### yarn の場合
$ yarn add swr
### npm の場合
$ npm install swr
先に、Game
インタフェースを共有できるように、ライブラリファイルとして抽出しておきます。
export type Game = {
id: string
title: string
genre: string
}
次のコンポーネントでは、クライアントサイド JavaScript で /api/games/1
というエンドポイントの API を呼び出しています。
useSWR
フックの型パラメーターとして Game
を指定することで、戻り値の data
変数の型が Game | undefined
になります(データ取得が完了するまで undefined
になる)。
import { NextPage } from 'next'
import useSWR from 'swr'
import type { GamesApiResponse } from '../api/games/[id]'
const HomePage: NextPage = () => {
const { data, error } = useSWR<GamesApiResponse, Error>('/api/games/1', fetcher)
if (error) return <p>Error: {error.message}</p>
if (!data) return <p>Loading...</p>
return (
<>
{data.game && <b>{data.game.title}</b>}
{data.debugMessage}
</>
)
}
export default HomePage
const fetcher = (url: string) => fetch(url).then((r) => r.json())
関連記事
- Next.js のダイナミックルーティング機能を利用する (getStaticPaths, getStaticProps, getServerSideProps)
- Next.js でコンポーネント内に直接 CSS を記述する (styled-jsx)
- Next.js で全ページ共通のレイアウトを定義する(Layout コンポーネント)
- Next.js で各ページの head 要素をカスタマイズする (next/head)
- Next.js のプリレンダリング機能を使用する (getStaticProps)
- Next.js のプロジェクトを TypeScript 化する
- Next.js アプリのソースコードを GitHub で管理する