概要
Next.js App Router の Route Handler(app/api/ 配下の route.ts)を使い、GET / POST / PUT / DELETE を備えた REST API を実装する。インメモリデータを使ってシンプルに CRUD 操作を示す。nextjs-server-actions-crud との違いは、外部クライアントからも利用できる REST API として公開すること。
インストール
# 追加インストールは不要
実装
型定義
// types/todo.ts
export type Todo = {
id: number;
title: string;
completed: boolean;
};
インメモリストア
// lib/todoStore.ts
import type { Todo } from "@/types/todo";
let todos: Todo[] = [
{ id: 1, title: "買い物をする", completed: false },
{ id: 2, title: "メールを返信する", completed: true },
];
let nextId = 3;
export const todoStore = {
getAll: () => todos,
getById: (id: number) => todos.find((t) => t.id === id),
create: (title: string) => {
const todo: Todo = { id: nextId++, title, completed: false };
todos.push(todo);
return todo;
},
update: (id: number, patch: Partial<Omit<Todo, "id">>) => {
const index = todos.findIndex((t) => t.id === id);
if (index === -1) return null;
todos[index] = { ...todos[index], ...patch };
return todos[index];
},
delete: (id: number) => {
const index = todos.findIndex((t) => t.id === id);
if (index === -1) return false;
todos.splice(index, 1);
return true;
},
};
一覧・作成 API(GET / POST)
// app/api/todos/route.ts
import { NextResponse } from "next/server";
import { todoStore } from "@/lib/todoStore";
export function GET() {
return NextResponse.json(todoStore.getAll());
}
export async function POST(request: Request) {
const body = await request.json();
const { title } = body as { title: string };
if (!title?.trim()) {
return NextResponse.json({ error: "title is required" }, { status: 400 });
}
const todo = todoStore.create(title.trim());
return NextResponse.json(todo, { status: 201 });
}
詳細・更新・削除 API(GET / PUT / DELETE)
// app/api/todos/[id]/route.ts
import { NextResponse } from "next/server";
import { todoStore } from "@/lib/todoStore";
type Params = { params: Promise<{ id: string }> };
export async function GET(_req: Request, { params }: Params) {
const { id } = await params;
const todo = todoStore.getById(Number(id));
if (!todo) return NextResponse.json({ error: "Not Found" }, { status: 404 });
return NextResponse.json(todo);
}
export async function PUT(request: Request, { params }: Params) {
const { id } = await params;
const body = await request.json();
const updated = todoStore.update(Number(id), body);
if (!updated) return NextResponse.json({ error: "Not Found" }, { status: 404 });
return NextResponse.json(updated);
}
export async function DELETE(_req: Request, { params }: Params) {
const { id } = await params;
const ok = todoStore.delete(Number(id));
if (!ok) return NextResponse.json({ error: "Not Found" }, { status: 404 });
return new NextResponse(null, { status: 204 });
}
クライアントからの呼び出し例
// 一覧取得
const res = await fetch("/api/todos");
const todos = await res.json();
// 作成
const res = await fetch("/api/todos", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title: "新しいタスク" }),
});
// 更新
await fetch("/api/todos/1", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ completed: true }),
});
// 削除
await fetch("/api/todos/1", { method: "DELETE" });
ポイント
- Route Handler は
app/api/<path>/route.tsに HTTP メソッド名と同名の関数を export することで定義する - Next.js 15 では動的ルートの
paramsがPromise型になったため、await paramsが必要 NextResponse.json()でレスポンスを返す。第2引数でステータスコードを指定できる- 204 No Content のように本文なしで返す場合は
new NextResponse(null, { status: 204 })を使う nextjs-server-actions-crudとの使い分け: 外部クライアント(モバイルアプリ等)や fetch による利用が想定される場合は Route Handler が適切