コンテンツにスキップ
ブログに戻る

2025年2月28日(金)

Next.js で API を構築する

投稿者

このガイドでは、Next.js を使用して API を構築する方法について説明します。プロジェクトの設定、App Router と Route Handlers の理解、複数の HTTP メソッドの処理、動的ルーティングの実装、再利用可能なミドルウェアロジックの作成、そして専用の API レイヤーをいつ設定するかといった点について解説します。

1. はじめに

1.1 Next.js アプリを作成する

新規に始める場合は、以下のコマンドで新しい Next.js プロジェクトを作成できます。

ターミナル
npx create-next-app@latest --api

注: --api フラグは、新しいプロジェクトの app/ フォルダに例となる route.ts を自動的に含み、API エンドポイントの作成方法を示します。

1.2 App Router vs Pages Router

  • Pages Router: 以前は、Next.js は API に pages/api/* を使用していました。このアプローチは Node.js の request/response オブジェクトと Express ライクな API に依存していました。
  • App Router (デフォルト): Next.js 13 で導入された App Router は、Web 標準の Request/Response API を完全に採用しています。pages/api/* の代わりに、app/ ディレクトリ内の任意の場所に route.ts または route.js ファイルを配置できるようになりました。

なぜ切り替えるのか? App Router の「Route Handlers」は、Node.js 固有の API ではなく、Web Platform Request/Response API に基づいています。これにより、学習が容易になり、摩擦が軽減され、さまざまなツール間で知識を再利用できるようになります。

2. Next.js で API を構築する理由(そしていつ)

  1. 複数のクライアント向け公開 API

    • Next.js ウェブアプリ、別のモバイルアプリ、または任意のサードパーティサービスで利用される公開 API を構築できます。たとえば、React ウェブサイトと React Native モバイルアプリの両方で /api/users からフェッチすることができます。
  2. 既存のバックエンドへのプロキシ

    • 外部のマイクロサービス を単一のエンドポイントの背後に隠したり、統合したりしたい場合があります。Next.js Route Handlers は、プロキシまたはミドルレイヤーとして既存のバックエンドに機能できます。たとえば、リクエストをインターセプトし、認証を処理し、データを変換してから、リクエストをアップストリーム API に渡すことができます。
  3. Webhooks とインテグレーション

    • 外部のコールバックや Webhook(Stripe、GitHub、Twilio など)を受信する場合、Route Handlers で処理できます。
  4. カスタム認証

    • セッション、トークン、その他の認証ロジックが必要な場合は、クッキーを保存し、ヘッダーを読み取り、Next.js API レイヤーで適切なデータで応答できます。

注: 独自の Next.js アプリケーションのサーバーサイドデータ取得のみが必要で、そのデータを外部と共有する必要がない場合は、Server Components がレンダリング中に直接データを取得するのに十分な場合があります。別途 API レイヤーは不要です。

3. Route Handlers で API を作成する

3.1 基本的なファイル設定

App Router (app/) では、ルートを表すフォルダを作成し、その中に route.ts ファイルを作成します。

たとえば、/api/users でエンドポイントを作成するには

app
└── api
    └── users
        └── route.ts

3.2 1 つのファイルで複数の HTTP メソッドを処理する

Pages Router の API ルート(単一のデフォルトエクスポートがあった)とは異なり、同じファイルから異なる HTTP メソッドを表す複数の関数をエクスポートできます。

app/api/users/route.ts
export async function GET(request: Request) {
  // For example, fetch data from your DB here
  const users = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' }
  ];
  return new Response(JSON.stringify(users), {
    status: 200,
    headers: { 'Content-Type': 'application/json' }
  });
}
 
export async function POST(request: Request) {
  // Parse the request body
  const body = await request.json();
  const { name } = body;
 
  // e.g. Insert new user into your DB
  const newUser = { id: Date.now(), name };
 
  return new Response(JSON.stringify(newUser), {
    status: 201,
    headers: { 'Content-Type': 'application/json' }
  });
}

これで、/api/users への GET リクエストはユーザーリストを返しますが、同じ URL への POST リクエストは新しいユーザーを挿入します。

4. Web API を操作する

4.1 Request & Response を直接使用する

デフォルトでは、Route Handler メソッド(GETPOST など)は標準の Request オブジェクトを受け取り、標準の Response オブジェクトを返す必要があります。

4.2 クエリパラメータ

app/api/search/route.ts
import { NextRequest } from 'next/server';
 
export function GET(request: NextRequest) {
  const searchParams = request.nextUrl.searchParams;
  const query = searchParams.get('query'); // e.g. `/api/search?query=hello`
 
  return new Response(
    JSON.stringify({ result: `You searched for: ${query}` }),
    {
      headers: { 'Content-Type': 'application/json' },
    },
  );
}

4.3 ヘッダーとクッキー

app/api/auth/route.ts
import { NextRequest } from 'next/server';
import { cookies, headers } from 'next/headers';
 
export async function GET(request: NextRequest) {
  // 1. Using 'next/headers' helpers
  const cookieStore = await cookies();
  const token = cookieStore.get('token');
 
  const headersList = await headers();
  const referer = headersList.get('referer');
 
  // 2. Using the standard Web APIs
  const userAgent = request.headers.get('user-agent');
 
  return new Response(JSON.stringify({ token, referer, userAgent }), {
    headers: { 'Content-Type': 'application/json' },
  });
}

cookies() および headers() 関数は、Next.js で他のサーバーサイドコードと共有ロジックを再利用する場合に役立ちます。Next.js は、基本の Web API を拡張する NextRequest および NextResponse も提供していることに気づくでしょう。

5. 動的ルート

動的パス(例: /api/users/:id)を作成するには、フォルダ構造で動的セグメントを使用します。

app
└── api
    └── users
        └── [id]
            └── route.ts

このファイルは、/api/users/123 のような URL に対応し、123 はパラメータとしてキャプチャされます。

app/api/users/[id]/route.ts
import { NextRequest } from 'next/server';
 
export async function GET(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> },
) {
  const id = (await params).id;
  // e.g. Query a database for user with ID `id`
  return new Response(JSON.stringify({ id, name: `User ${id}` }), {
    status: 200,
    headers: { 'Content-Type': 'application/json' },
  });
}
 
export async function DELETE(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> },
) {
  const id = (await params).id;
  // e.g. Delete user with ID `id` in DB
  return new Response(null, { status: 204 });
}

ここで、params.id で動的セグメントを取得できます。

6. プロキシまたはフォワーディングレイヤーとして Next.js を使用する

一般的なシナリオは、既存のバックエンドサービスをプロキシすることです。リクエストを認証したり、ロギングを処理したり、リモートサーバーやバックエンドに送信する前にデータを変換したりできます。

app/api/external/route.ts
import { NextRequest } from 'next/server';
 
export async function GET(request: NextRequest) {
  const response = await fetch('https://example.com/api/data', {
    // Optional: forward some headers, add auth tokens, etc.
    headers: { Authorization: `Bearer ${process.env.API_TOKEN}` },
  });
 
  // Transform or forward the response
  const data = await response.json();
  const transformed = { ...data, source: 'proxied-through-nextjs' };
 
  return new Response(JSON.stringify(transformed), {
    headers: { 'Content-Type': 'application/json' },
  });
}

これで、クライアントは /api/external を呼び出すだけでよく、残りは Next.js が処理します。これは「Backend for Frontend」または BFF とも呼ばれます。

7. 共有「ミドルウェア」ロジックの構築

複数の Route Handlers にわたって同じロジック(例: 認証チェック、ロギング)を適用したい場合、ハンドラーをラップする再利用可能な関数を作成できます。

lib/with-auth.ts
import { NextRequest } from 'next/server';
 
type Handler = (req: NextRequest, context?: any) => Promise<Response>;
 
export function withAuth(handler: Handler): Handler {
  return async (req, context) => {
    const token = req.cookies.get('token')?.value;
    if (!token) {
      return new Response(JSON.stringify({ error: 'Unauthorized' }), {
        status: 401,
        headers: { 'Content-Type': 'application/json' },
      });
    }
 
    // If authenticated, call the original handler
    return handler(req, context);
  };
}

次に Route Handler で

app/api/secret/route.ts
import { NextRequest } from 'next/server';
import { withAuth } from '@/lib/with-auth';
 
async function secretGET(request: NextRequest) {
  return new Response(JSON.stringify({ secret: 'Here be dragons' }), {
    headers: { 'Content-Type': 'application/json' },
  });
}
 
export const GET = withAuth(secretGET);

8. デプロイメントと「SPA モード」に関する考慮事項

8.1 標準的な Node.js デプロイメント

Next.js サーバーデプロイメントの標準である next start は、Route Handlers、Server Components、Middleware などの機能を使用できるようにします。また、動的なリクエストタイム情報も活用できます。

追加の設定は不要です。「デプロイメント」を参照してください。

8.2 SPA/静的エクスポート

Next.js は、サイト全体を静的な Single-Page Application (SPA) として出力することもサポートしています。

これには、以下を設定します。

next.config.ts
import type { NextConfig } from 'next';
 
const nextConfig: NextConfig = {
  output: 'export',
};
 
export default nextConfig;

静的エクスポートモードでは、Next.js は純粋に静的な HTML、CSS、JS を生成します。サーバーサイドコード(API エンドポイントなど)は実行できません。API が still 必要な場合は、別途ホストする必要があります(例: スタンドアロン Node.js サーバー)。

注意

  • GET Route Handlers は、動的なリクエストデータに依存しない場合、静的にエクスポートできます。これらは out フォルダ内の静的ファイルになります。
  • その他のサーバー機能(動的リクエスト、クッキーの書き換えなど)は、純粋な SPA エクスポートではサポートされていません

8.3 Vercel への API デプロイ

Next.js アプリケーションを Vercel にデプロイする場合、API のデプロイに関するガイド があります。これには、Vercel Firewall を介したプログラムによるレート制限 などの他の Vercel 機能も含まれています。Vercel は、API アプローチで一般的に必要とされるCron Jobs も提供しています。

9. API エンドポイントの作成をスキップすべき場合

App Router のReact Server Components を使用すると、公開エンドポイントを公開せずに、サーバー上で直接データを取得できます。

app/users/page.tsx
// (Server Component)
export default async function UsersPage() {
  // This fetch runs on the server (no client-side code needed here)
  const res = await fetch('https://api.example.com/users');
  const data = await res.json();
 
  return (
    <main>
      <h1>Users</h1>
      <ul>
        {data.map((user: any) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </main>
  );
}

データが Next.js アプリケーション内でのみ使用される場合、公開 API はまったく必要ないかもしれません。

10. すべてをまとめる

  1. 新しい Next.js プロジェクトを作成する: npx create-next-app@latest --api
  2. app/ ディレクトリ内に Route Handlers を追加する(例: app/api/users/route.ts)。
  3. HTTP メソッドをエクスポートするGETPOSTPUTDELETE など)を同じファイルに記述します。
  4. Web Standard APIs を使用して Request オブジェクトとやり取りし、Response を返します。
  5. 公開 API を構築する。他のクライアントがデータを消費する必要がある場合、またはバックエンドサービスをプロキシする場合。
  6. クライアントから新しい API ルートをフェッチする(例: Client Component 内または fetch('/api/...') を使用)。
  7. または、Server Component がデータをフェッチするだけで済む場合は、API の作成をスキップする
  8. 共有「ミドルウェア」パターンを追加する(例: withAuth())を、認証やその他の繰り返しロジックに使用します。
  9. サーバー機能には Node.js 対応環境にデプロイするか、静的な SPA のみが必要な場合は静的にエクスポートする

結論

Next.js のApp RouterRoute Handlers を使用すると、Web Platform を直接活用した、柔軟でモダンな API 構築方法が実現します。以下のようなことが可能です。

  • Web、モバイル、またはサードパーティクライアントで共有される、完全な公開 API を作成する
  • 既存の外部サービスへの呼び出しをプロキシおよびカスタマイズする
  • 認証、ロギング、または任意の繰り返しロジックのための、再利用可能な「ミドルウェア」レイヤーを実装する
  • [id] セグメントフォルダ構造を使用して、リクエストを動的にルーティングする

よくある質問

Server Actions について

Server Actions は、クライアントから呼び出せる、自動生成された POST API ルートのようなものだと考えることができます。

これらは、データの作成、更新、削除など、ミューテーション操作のために設計されています。Server Action は、明示的な fetch を定義された API ルートに行うのではなく、通常の JavaScript 関数のように呼び出します。

ネットワークリクエストは実際に行われていますが、明示的に管理する必要はありません。URL パスは自動生成され、暗号化されているため、ブラウザで /api/users のようなルートに手動でアクセスすることはできません。

Server Actions と公開 API の両方を使用する予定がある場合は、コアロジックをデータアクセスレイヤーに移動し、Server Action と API ルートの両方から同じロジックを呼び出すことをお勧めします。

Route Handlers で TypeScript は使用できますか?

はい、Route Handlers で TypeScript を使用できます。たとえば、route ファイルで Request および Response の型を定義します。

Next.js での TypeScript については、こちらで詳しく学べます。

認証のベストプラクティスは何ですか?

認証に関するドキュメントで、詳細をご覧ください。