まくろぐ

Next.js で Web API を作成する (API Routes)

更新:
作成:

Next.js の Web API 機能

Next.js では、pages/api ディレクトリ以下に TypeScript (JavaScript) コードを配置するだけで、クライアントサイド JavaScript から呼び出せる API を定義することができます。

例えば、次のようなファイルを作成します。

pages/api/hello.ts
import type { NextApiRequest, NextApiResponse } from 'next'

type Person = {
  name: string;
}

export default (req: NextApiRequest, res: NextApiResponse<Person>) => {
  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 のサービス などを使ってホスティングすることになります。

クエリパラメーターに対応する

例えば、ゲームの情報を取得する API として /api/games というエンドポイントを定義するとします。 パラメーターとして 1 などのゲーム ID を指定する場合、次のような 2 通りの指定方法が考えられます。

  • /api/games?id=1
  • /api/games/1

以下、それぞれの実装方法を説明します。

/api/games?id=1 という指定方法

URL の末尾に ?id=1 のように指定されたクエリパラメーターは、API の実装の中で req.query.id という形で参照できます。

pages/api/games.ts
import type { NextApiRequest, NextApiResponse } from 'next'

type Game = {
  id: string;
  title: string;
  genre: string;
}

// 本来は外部のデータを取得する
function getGameData(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 のエントリポイント
export default (req: NextApiRequest, res: NextApiResponse<Game | undefined>) => {
  const id = req.query.id
  const game = getGameData(id as string)
  res.status(200).json(game)
}

/api/games?id=2 というアドレスでアクセスすると、次のような JSON データが返されます。

{"id":"2","title":"ゼビウス","genre":"シューティング"}

/api/games/1 という指定方法

REST API 風の URL でアクセスしたいときは、ダイナミックルーティングの機能を使って API を実装します。 例えば、/api/games/1/api/games/2 のような URL をハンドルするには、pages/api/games/[id].ts というファイルを作成します。 id 部分に指定されたパラメーターを取得する方法は、前述の方法と同じで req.query.id で参照できます。

games/api/games/[id].ts
// ...実装方法は同じ...
export default (req: NextApiRequest, res: NextApiResponse<Game | undefined>) => {
  const id = req.query.id
  const game = getGameData(id as string)
  res.status(200).json(game)
}

/api/games/3 というアドレスでアクセスすると、次のような JSON データが返されます。

{"id":"3","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 パッケージとして提供しています。

swr パッケージのインストール
### yarn の場合
$ yarn add swr

### npm の場合
$ npm install swr

先に、Game インタフェースを共有できるように、ライブラリファイルとして抽出しておきます。

libs/types.ts
export type Game = {
  id: string;
  title: string;
  genre: string;
}

次のコンポーネントでは、クライアントサイド JavaScript で /api/games/1 というエンドポイントの API を呼び出しています。 useSWR フックの型パラメーターとして Game を指定することで、戻り値の data 変数を Game 型として参照できます。

pages/home.tsx
import React from 'react'
import type { Game } from '../libs/types'
import useSWR from 'swr'

const Home: React.FC = () => {
  const { data, error } = useSWR<Game>('/api/games/1')
  if (error) return <p>Failed to load</p>
  if (!data) return <p>Loading...</p>

  return <p>{data.id}, {data.title}, {data.genre}</p>
}

export default Home

関連記事

まくろぐ
サイトマップまくへのメッセージ