Next.jsのルートハンドラーを使用して強力なAPIエンドポイントを作成する方法を学びます。このガイドでは、基本的な設定から高度なテクニックまで、実践的な例やベストプラクティスを網羅しています。
Next.js ルートハンドラー: APIエンドポイント作成のための総合ガイド
Next.jsは、サーバーサイドレンダリング、静的サイト生成、そして現在のルートハンドラーといった強力な機能により、Webアプリケーションの構築方法に革命をもたらしました。ルートハンドラーは、Next.jsアプリケーション内に直接APIエンドポイントを作成するための、柔軟かつ効率的な方法を提供します。このガイドでは、ルートハンドラーの概念、その利点、そして堅牢なAPIを構築するために効果的に使用する方法について探ります。
Next.js ルートハンドラーとは?
ルートハンドラーは、Next.jsプロジェクトのapp
ディレクトリ内に定義され、受信HTTPリクエストを処理する関数です。古いpages/api
アプローチ(APIルートを使用)とは異なり、ルートハンドラーはReactコンポーネントと並行してAPIエンドポイントを定義するための、より洗練され柔軟な方法を提供します。これらは本質的に、エッジまたは選択したサーバー環境で実行されるサーバーレス関数です。
ルートハンドラーは、リクエストの処理、データベースとの対話、レスポンスの返却を担当する、Next.jsアプリケーションのバックエンドロジックと考えてください。
ルートハンドラーを使用する利点
- コロケーション: ルートハンドラーは
app
ディレクトリ内のReactコンポーネントのすぐ隣に配置されるため、より良い整理とコードの保守性が促進されます。 - TypeScriptサポート: 組み込みのTypeScriptサポートにより、型安全性が確保され、開発者体験が向上します。
- ミドルウェア統合: 認証、認可、リクエスト検証などのタスクにミドルウェアを簡単に統合できます。
- ストリーミングサポート: ルートハンドラーはデータをストリーミングできるため、レスポンスを段階的に送信することが可能になり、大規模なデータセットや長時間実行されるプロセスに有益です。
- エッジ関数: ルートハンドラーをエッジ関数としてデプロイすることで、グローバルCDNを活用し、ユーザーに近い場所で低遅延のレスポンスを実現します。
- 簡素化されたAPI設計: ルートハンドラーは、リクエストとレスポンスを処理するためのクリーンで直感的なAPIを提供します。
- サーバーアクションとの統合: サーバーアクションとの緊密な統合により、クライアントサイドコンポーネントとサーバーサイドロジック間のシームレスな通信が可能になります。
Next.jsプロジェクトのセットアップ
ルートハンドラーに取り掛かる前に、app
ディレクトリを持つNext.jsプロジェクトがセットアップされていることを確認してください。新しいプロジェクトを開始する場合は、次のコマンドを使用します:
npx create-next-app@latest my-nextjs-app
セットアッププロセス中にapp
ディレクトリを選択して、新しいルーティングシステムを有効にしてください。
最初のルートハンドラーを作成する
JSONレスポンスを返す簡単なAPIエンドポイントを作成しましょう。app
ディレクトリ内に新しいディレクトリ、例えば/app/api/hello
を作成します。このディレクトリ内に、route.ts
(TypeScriptを使用していない場合はroute.js
)という名前のファイルを作成します。
これが最初のルートハンドラーのコードです:
// app/api/hello/route.ts
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
return NextResponse.json({ message: 'Hello from Next.js Route Handlers!' });
}
解説:
import { NextResponse } from 'next/server';
: APIレスポンスを構築するために使用されるNextResponse
オブジェクトをインポートします。export async function GET(request: Request) { ... }
:/api/hello
エンドポイントへのGETリクエストを処理する非同期関数を定義します。request
パラメータは、受信リクエストオブジェクトへのアクセスを提供します。return NextResponse.json({ message: 'Hello from Next.js Route Handlers!' });
: メッセージを含むJSONレスポンスを作成し、NextResponse.json()
を使用してそれを返します。
これで、ブラウザで/api/hello
にアクセスするか、curl
やPostman
のようなツールを使用してこのエンドポイントにアクセスできます。
異なるHTTPメソッドの処理
ルートハンドラーは、GET、POST、PUT、DELETE、PATCH、OPTIONSなどのさまざまなHTTPメソッドをサポートしています。同じroute.ts
ファイル内で、各メソッドに対して別々の関数を定義できます。
// app/api/users/route.ts
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
// データベースから全ユーザーを取得するロジック
const users = [{ id: 1, name: 'John Doe' }, { id: 2, name: 'Jane Smith' }]; // サンプルデータ
return NextResponse.json(users);
}
export async function POST(request: Request) {
const data = await request.json(); // リクエストボディをJSONとしてパース
// 'data'を使用してデータベースに新規ユーザーを作成するロジック
const newUser = { id: 3, name: data.name, email: data.email }; // 例
return NextResponse.json(newUser, { status: 201 }); // 201 Createdステータスコードと共に新しいユーザーを返す
}
解説:
GET
関数はユーザーのリスト(ここではシミュレート)を取得し、JSONレスポンスとして返します。POST
関数はリクエストボディをJSONとしてパースし、新しいユーザーを作成(シミュレート)し、201 Createdステータスコードと共に新しいユーザーを返します。
リクエストデータへのアクセス
request
オブジェクトは、ヘッダー、クエリパラメータ、リクエストボディなど、受信リクエストに関するさまざまな情報へのアクセスを提供します。
ヘッダー
request.headers
プロパティを使用してリクエストヘッダーにアクセスできます:
export async function GET(request: Request) {
const userAgent = request.headers.get('user-agent');
console.log('User Agent:', userAgent);
return NextResponse.json({ userAgent });
}
クエリパラメータ
クエリパラメータにアクセスするには、URL
コンストラクタを使用できます:
export async function GET(request: Request) {
const url = new URL(request.url);
const searchParams = new URLSearchParams(url.search);
const id = searchParams.get('id');
console.log('ID:', id);
return NextResponse.json({ id });
}
リクエストボディ
POST、PUT、PATCHリクエストの場合、コンテントタイプに応じてrequest.json()
またはrequest.text()
メソッドを使用してリクエストボディにアクセスできます。
export async function POST(request: Request) {
const data = await request.json();
console.log('Data:', data);
return NextResponse.json({ receivedData: data });
}
レスポンスの返却
NextResponse
オブジェクトはAPIレスポンスを構築するために使用されます。ヘッダー、ステータスコード、レスポンスボディを設定するためのいくつかのメソッドを提供します。
JSONレスポンス
JSONレスポンスを返すにはNextResponse.json()
メソッドを使用します:
return NextResponse.json({ message: 'Success!', data: { name: 'John Doe' } }, { status: 200 });
テキストレスポンス
プレーンテキストのレスポンスを返すにはnew Response()
コンストラクタを使用します:
return new Response('Hello, world!', { status: 200, headers: { 'Content-Type': 'text/plain' } });
リダイレクト
ユーザーを別のURLにリダイレクトするにはNextResponse.redirect()
を使用します:
import { redirect } from 'next/navigation';
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
return NextResponse.redirect(new URL('/new-location', request.url));
}
ヘッダーの設定
NextResponse.json()
またはnew Response()
のheaders
オプションを使用してカスタムヘッダーを設定できます:
return NextResponse.json({ message: 'Success!' }, { status: 200, headers: { 'Cache-Control': 'no-cache' } });
ミドルウェア統合
ミドルウェアを使用すると、リクエストがルートハンドラーによって処理される前にコードを実行できます。これは認証、認可、ロギングなどの横断的関心事に役立ちます。
ミドルウェアを作成するには、app
ディレクトリまたはそのサブディレクトリにmiddleware.ts
(またはmiddleware.js
)という名前のファイルを作成します。ミドルウェアはそのディレクトリおよびそのサブディレクトリ内のすべてのルートに適用されます。
// app/middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const token = request.cookies.get('auth-token');
if (!token) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/protected/:path*'], // このミドルウェアを/protected/で始まるパスに適用
};
解説:
middleware
関数は、リクエストのクッキーに認証トークンがあるかを確認します。- トークンがない場合、ユーザーをログインページにリダイレクトします。
- それ以外の場合、リクエストがルートハンドラーに進むのを許可します。
config
オブジェクトは、このミドルウェアが/protected/
で始まるルートにのみ適用されるべきことを指定します。
エラーハンドリング
堅牢なAPIを構築するためには、適切なエラーハンドリングが不可欠です。try...catch
ブロックを使用して例外を処理し、適切なエラーレスポンスを返すことができます。
export async function GET(request: Request) {
try {
// エラーをシミュレート
throw new Error('Something went wrong!');
} catch (error: any) {
console.error('Error:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
解説:
try...catch
ブロックは、ルートハンドラー内で発生した例外をキャッチします。catch
ブロックでは、エラーがログに記録され、500 Internal Server Errorステータスコードを持つエラーレスポンスが返されます。
ストリーミングレスポンス
ルートハンドラーはストリーミングレスポンスをサポートしており、クライアントにデータを段階的に送信することができます。これは特に大規模なデータセットや長時間実行されるプロセスに役立ちます。
import { Readable } from 'stream';
import { NextResponse } from 'next/server';
async function* generateData() {
for (let i = 0; i < 10; i++) {
await new Promise(resolve => setTimeout(resolve, 500)); // 遅延をシミュレート
yield `Data chunk ${i}\n`;
}
}
export async function GET(request: Request) {
const readableStream = Readable.from(generateData());
return new Response(readableStream, {
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
});
}
解説:
generateData
関数は、遅延を伴ってデータチャンクを生成する非同期ジェネレータです。Readable.from()
メソッドは、ジェネレータから読み取り可能なストリームを作成します。Response
オブジェクトは、ボディとして読み取り可能なストリームを使用して作成され、Content-Type
ヘッダーはtext/plain
に設定されます。
認証と認可
APIエンドポイントのセキュリティ確保は非常に重要です。ミドルウェアを使用するか、ルートハンドラー内で直接、認証と認可を実装できます。
認証
認証は、リクエストを行っているユーザーの身元を確認します。一般的な認証方法には以下があります:
- JWT (JSON Web Tokens): ログイン成功時にトークンを生成し、後続のリクエストでそれを検証します。
- セッションベース認証: クッキーを使用してセッション識別子を保存し、各リクエストでそれを検証します。
- OAuth: GoogleやFacebookなどのサードパーティプロバイダーに認証を委任します。
以下は、ミドルウェアを使用したJWT認証の例です:
// app/middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import jwt from 'jsonwebtoken';
const secret = process.env.JWT_SECRET || 'your-secret-key'; // 強力でランダムに生成されたシークレットに置き換えてください
export function middleware(request: NextRequest) {
const token = request.cookies.get('auth-token')?.value;
if (!token) {
return NextResponse.json({ message: 'Authentication required' }, { status: 401 });
}
try {
jwt.verify(token, secret);
return NextResponse.next();
} catch (error) {
return NextResponse.json({ message: 'Invalid token' }, { status: 401 });
}
}
export const config = {
matcher: ['/api/protected/:path*'],
};
認可
認可は、ユーザーがどのリソースにアクセスできるかを決定します。これは通常、ロールや権限に基づいています。
ルートハンドラー内でユーザーのロールや権限を確認し、アクセス権がない場合はエラーを返すことで認可を実装できます。
// app/api/admin/route.ts
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
// トークンやセッションからユーザーのロールを取得する関数があると仮定
const userRole = await getUserRole(request);
if (userRole !== 'admin') {
return NextResponse.json({ message: 'Unauthorized' }, { status: 403 });
}
// 管理者データを取得するロジック
const adminData = { message: 'Admin data' };
return NextResponse.json(adminData);
}
async function getUserRole(request: Request): Promise {
// リクエストからユーザーのロールを抽出する実際のロジックに置き換えてください
// JWTトークンの検証やセッションの確認などが考えられます
return 'admin'; // 例:デモンストレーション用のハードコードされたロール
}
ルートハンドラーのデプロイ
ルートハンドラーは、選択したホスティングプロバイダー上でサーバーレス関数としてデプロイされます。Next.jsはVercel、Netlify、AWSなど、さまざまなデプロイプラットフォームをサポートしています。
Vercelの場合、デプロイはGitリポジトリをVercelに接続し、コードをプッシュするだけと簡単です。Vercelは自動的にNext.jsプロジェクトを検出し、ルートハンドラーをサーバーレス関数としてデプロイします。
高度なテクニック
エッジ関数
ルートハンドラーはエッジ関数としてデプロイでき、これはユーザーに近いCDNのエッジで実行されます。これにより、遅延が大幅に削減され、パフォーマンスが向上します。
ルートハンドラーをエッジ関数としてデプロイするには、route.ts
ファイルにedge
ランタイムを追加します:
export const runtime = 'edge';
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
return NextResponse.json({ message: 'Hello from the Edge!' });
}
サーバーアクション
サーバーアクションを使用すると、Reactコンポーネントから直接サーバーサイドのコードを実行できます。ルートハンドラーとサーバーアクションはシームレスに連携し、複雑なアプリケーションを簡単に構築できます。
以下は、サーバーアクションを使用してルートハンドラーを呼び出す例です:
// app/components/MyComponent.tsx
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
async function handleSubmit(data: FormData) {
'use server';
const name = data.get('name');
const email = data.get('email');
const response = await fetch('/api/users', {
method: 'POST',
body: JSON.stringify({ name, email }),
});
if (response.ok) {
router.refresh(); // 変更を反映するためにページをリフレッシュ
}
}
export default function MyComponent() {
const router = useRouter();
return (
);
}
キャッシング
キャッシングはAPIエンドポイントのパフォーマンスを大幅に向上させることができます。Cache-Control
ヘッダーを使用して、ブラウザやCDNによるレスポンスのキャッシュ方法を制御できます。
return NextResponse.json({ message: 'Success!' }, { status: 200, headers: { 'Cache-Control': 'public, max-age=3600' } });
この例では、Cache-Control
ヘッダーをpublic, max-age=3600
に設定しており、ブラウザとCDNにレスポンスを1時間キャッシュするように指示します。
ベストプラクティス
- TypeScriptを使用する: TypeScriptの型安全性を活用して、コードの品質を向上させ、エラーを防ぎます。
- リクエストを検証する: 受信リクエストを検証して、データの整合性を確保し、悪意のある入力を防ぎます。
- エラーを適切に処理する: 適切なエラーハンドリングを実装して、クライアントに有益なエラーメッセージを提供します。
- エンドポイントを保護する: 認証と認可を実装して、APIエンドポイントを保護します。
- ミドルウェアを使用する: 認証、ロギング、リクエスト検証などの横断的関心事にはミドルウェアを使用します。
- レスポンスをキャッシュする: キャッシングを使用して、APIエンドポイントのパフォーマンスを向上させます。
- APIを監視する: APIを監視して、問題を迅速に特定し解決します。
- APIを文書化する: APIを文書化して、他の開発者が使いやすくします。APIドキュメントにはSwagger/OpenAPIのようなツールの使用を検討してください。
実世界の例
以下は、ルートハンドラーがどのように使用できるかの実世界の例です:
- EコマースAPI: 製品、注文、ユーザーを管理するためのAPIエンドポイントを作成します。
- ソーシャルメディアAPI: ツイートの投稿、ユーザーのフォロー、タイムラインの取得のためのAPIエンドポイントを作成します。
- コンテンツ管理システム (CMS) API: コンテンツ、ユーザー、設定を管理するためのAPIエンドポイントを作成します。
- データ分析API: データを収集・分析するためのAPIエンドポイントを作成します。例えば、ルートハンドラーは異なるウェブサイトのトラッキングピクセルからデータを受け取り、レポート用に情報を集約できます。
国際Eコマースの例: ユーザーの国に基づいて製品価格を取得するために使用されるルートハンドラー。エンドポイントはリクエストの地理情報(IPアドレスから導出)を使用してユーザーの場所を特定し、適切な通貨で価格を返すことができます。これは、ローカライズされたショッピング体験に貢献します。
グローバル認証の例: 世界中のユーザー向けに多要素認証(MFA)を実装するルートハンドラー。これには、SMSコードの送信や認証アプリの使用が含まれ、同時に異なる地域のプライバシー規制や通信インフラを尊重します。
多言語コンテンツ配信: ユーザーの優先言語でコンテンツを配信するルートハンドラー。これはリクエストの`Accept-Language`ヘッダーから判断できます。この例は、適切なUTF-8エンコーディングと、必要に応じた右から左への言語サポートの必要性を強調しています。
結論
Next.jsのルートハンドラーは、Next.jsアプリケーション内に直接APIエンドポイントを作成するための強力で柔軟な方法を提供します。ルートハンドラーを活用することで、堅牢なAPIを簡単に構築し、バックエンドロジックをReactコンポーネントとコロケーションさせ、ミドルウェア、ストリーミング、エッジ関数などの機能を利用できます。
この総合ガイドでは、基本的な設定から高度なテクニックまでを網羅しました。このガイドで概説されたベストプラクティスに従うことで、安全で、パフォーマンスが高く、保守可能な高品質のAPIを構築できます。