はじめに
Next.js の App Router で EC サイトの注文管理 API を実装する中で、リクエストの受け取り方・型安全・例外処理・アーキテクチャ設計について多くの学びがありました。この記事では、API Route 実装時につまずきやすいポイントを整理してまとめます。
この記事の対象読者・前提条件
対象読者
- Next.js App Router で API Route を書き始めた方
- TypeScript の型安全をどこまでやるべきか迷っている方
前提条件
- Next.js の基本的なプロジェクト構成を理解している
- Prisma の基本操作(CRUD)を知っている
実装
1. request.json() でのデータ受け取り方
request.json() は非同期関数なので、必ず await が必要です。
// ❌ await がないと Promise オブジェクトが返る
const { status } = request.json();
// ✅ await でデータの到着を待つ
const { status } = await request.json();
解説:
リクエストボディのデータはネットワーク経由で送られてくるため、すべてのデータが届くまで待つ必要があります。await をつけないと、データの中身ではなく「データを取得中」という Promise オブジェクトが返ってきてしまいます。Java で例えると、InputStream からデータを読み取る操作に近い考え方です。
また、リクエストボディの受け取りはシンプルに分割代入で書けます。
// ❌ 不要なラッパーオブジェクトで受け取る
const { paymentRequest } = await request.json();
const amount = paymentRequest.amount;
// ✅ 直接必要な値を取り出す
const { amount } = await request.json();
2. request.json() の型安全
request.json() の戻り値は any 型になります。TypeScript はリクエストの中身を知りようがないためです。
// ❌ any 型のまま使うと型チェックが効かない
const { userId, address, items } = await request.json();
// items.map((item) => ...) の item も any 型になる
// ✅ 型を定義して指定する
type OrderRequest = {
userId: string;
address: string;
items: {
productId: string;
quantity: number;
price: number;
}[];
};
const { userId, address, items } = await request.json() as OrderRequest;
解説:
型定義が必要かどうかの判断基準は「TypeScript が自分で型を判断できるかどうか」です。特に外部からやってくるデータ(API レスポンス、リクエストボディ)は型を教えてあげる必要があります。逆に useState("") のように初期値から推論できる場合は不要です。
3. オプショナルチェーンの不要な使用
// ❌ ガード処理の後なのに ?. を使っている
if (!session?.user?.email) {
return NextResponse.json({ message: "未認証" }, { status: 401 });
}
const email = session.user?.email; // ← 不要な ?.
// ✅ ガード後は安全にアクセスできる
if (!session?.user?.email) {
return NextResponse.json({ message: "未認証" }, { status: 401 });
}
const email = session.user.email; // ← ?. 不要
解説:
ガード処理(early return)で email の存在を保証した後は、オプショナルチェーン ?. は不要です。むしろ不要な ?. があると「この値は null かもしれない」という誤ったメッセージをコードの読者に伝えてしまいます。
4. NextResponse.json() の引数の違い
// ❌ ステータスコードがボディに入ってしまう
return NextResponse.json({ status: 200 });
// ✅ 第1引数がボディ、第2引数が設定
return NextResponse.json({ orders: result }, { status: 200 });
// ✅ 200 はデフォルトなので省略可能
return NextResponse.json({ orders: result });
解説:
- 第1引数:レスポンスのボディ(クライアントに返すデータ)
- 第2引数:レスポンスの設定(ステータスコードなど)
レスポンスのキー名も data のような汎用的な名前より、orders のように中身が明確な名前にすると可読性が上がります。
5. 例外処理
// ❌ try/catch がない(DB 接続エラーや存在しない ID に対応できない)
export async function PATCH(
request: Request,
{ params }: { params: { id: string } }
) {
const { status } = await request.json();
const result = await prisma.order.update({
where: { id: params.id },
data: { status },
});
return NextResponse.json({ data: result });
}
// ✅ エラーハンドリングを追加
export async function PATCH(
request: Request,
{ params }: { params: { id: string } }
) {
try {
const { status } = await request.json();
const result = await prisma.order.update({
where: { id: params.id },
data: { status },
});
return NextResponse.json({ data: result });
} catch (error) {
return NextResponse.json(
{ message: "更新に失敗しました" },
{ status: 500 }
);
}
}
フロント側でも response.ok のチェックが必要です。
const response = await fetch("/api/orders");
if (!response.ok) {
// エラー処理
}
const data = await response.json();
6. useEffect 依存配列の注意点
// ❌ params.id を使っているのに依存配列が空
useEffect(() => {
fetch(`/api/orders/${params.id}`);
}, []);
// ✅ 使っている外部の値を依存配列に入れる
useEffect(() => {
fetch(`/api/orders/${params.id}`);
}, [params.id]);
解説:
依存配列には「この値が変わったら再実行してほしい」ものをすべて入れます。params.id が変わっても再フェッチされないと、古いデータが表示されたままになります。
アーキテクチャ:レイヤー構造と責務分離
EC サイトの注文管理機能のアーキテクチャを整理すると、以下のようになります。
[クライアント層]
orders/page.tsx — 注文一覧(表示 + fetch)
orders/[id]/page.tsx — 注文詳細(表示 + fetch)
components/OrderStatusBadge.tsx — ステータス表示
[API層]
api/orders/route.ts — GET(一覧)/ POST(注文作成)
api/orders/[id]/route.ts — GET(詳細)/ PATCH(ステータス更新)
[データ層]
prisma/schema.prisma — Order / OrderItem モデル
[型定義層]
types/order.ts — OrderStatus 等
依存関係のポイント:
- フロントは API を通じてのみ DB にアクセスする(直接 Prisma は呼ばない)
- 型定義は
types/で一元管理し、フロント・API の両方で共有する - UI コンポーネントは props だけに依存し、API や DB には依存しない
まとめ
-
request.json()は非同期関数なのでawaitが必須。戻り値はany型になるため、型定義を付けて型安全にする -
NextResponse.json()の第1引数はボディ、第2引数は設定。ステータスコードはボディに入れない - ガード処理後のオプショナルチェーンは不要。例外処理(try/catch)と
response.okチェックを忘れずに