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

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 と Pages Router

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

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

2. Next.jsでAPIを構築する理由(とタイミング)

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

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

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

    • 外部からのコールバックや 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 メソッド (GET, POST など) は標準の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 start を使用した標準の Next.js サーバーデプロイメントでは、Route Handlers、Server Components、Middleware などの機能を使用でき、動的なリクエスト時情報を活用できます。

追加の設定は不要です。詳細については、デプロイ を参照してください。

8.2 SPA/静的エクスポート

Next.js は、サイト全体を静的シングルページアプリケーション (SPA) として出力することもサポートしています。

これを有効にするには、次のように設定します。

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

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

  • GET Route Handlers は、動的なリクエストデータに依存しない場合、静的にエクスポートできます。それらは出力フォルダー内の静的ファイルになります。
  • その他のすべてのサーバー機能 (動的リクエスト、クッキーの書き換えなど) は、純粋な 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 メソッド (GET, POST, PUT, DELETE など) をエクスポートする
  4. Request オブジェクトとやり取りし、Response を返すためにWeb標準APIを使用する
  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 ルートと考えることができます。

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

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

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

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

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

Next.js と TypeScript の詳細については、こちらをご覧ください。

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

詳細については、認証ドキュメントをご覧ください。