目次
1. データの「読み取り」(Read)
まず、既存のデータを表示するところから始めましょう。
考え方:
Next.js のコンポーネント(または RSC - React Server Components)でデータをフェッチし、表示します。API ルートを使う場合は、フロントエンドからその API ルートを呼び出します。
実装例(API ルート + クライアントコンポーネント):
JavaScript
// app/api/tasks/route.js (Route Handler)
import { NextResponse } from 'next/server';
import prisma from '@/lib/prisma'; // PrismaなどのORMを想定
export async function GET() {
try {
const tasks = await prisma.task.findMany(); // 全てのタスクを取得
return NextResponse.json(tasks);
} catch (error) {
return NextResponse.json({ error: 'Failed to fetch tasks' }, { status: 500 });
}
}
JavaScript
// app/tasks/page.jsx (Client Component)
'use client'; // クライアントコンポーネントであることを宣言
import { useEffect, useState } from 'react';
export default function TasksPage() {
const [tasks, setTasks] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchTasks() {
try {
const response = await fetch('/api/tasks'); // APIルートを呼び出し
if (!response.ok) {
throw new Error('Failed to fetch tasks');
}
const data = await response.json();
setTasks(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
fetchTasks();
}, []);
if (loading) return <p>Loading tasks...</p>;
if (error) return <p>Error: {error}</p>;
return (
<div>
<h1>My Tasks</h1>
<ul>
{tasks.map((task) => (
<li key={task.id}>{task.title}</li>
))}
</ul>
</div>
);
}
ポイント:
app/api/tasks/route.js
でGET
リクエストを処理する API ルートを作成。app/tasks/page.jsx
のクライアントコンポーネントでuseEffect
を使ってデータをフェッチ。- Prisma のような ORM を利用すると、データベース操作が簡単になります。
2. データの「作成」(Create)
新しいデータを追加する方法です。
考え方:
フォームからユーザー入力を受け取り、API ルートに POST リクエストとして送信します。
実装例:
JavaScript
// app/api/tasks/route.js (POSTメソッドを追加)
// ... GETメソッドの上に追記 ...
export async function POST(request) {
try {
const { title } = await request.json(); // リクエストボディからタイトルを取得
const newTask = await prisma.task.create({
data: { title },
});
return NextResponse.json(newTask, { status: 201 }); // 成功時は201 Created
} catch (error) {
return NextResponse.json({ error: 'Failed to create task' }, { status: 500 });
}
}
JavaScript
// app/tasks/page.jsx (フォームを追加)
// ... 既存のコードの下に追記 ...
import { useState } from 'react'; // useStateをインポート
export default function TasksPage() {
// ... 既存のtasks, loading, error state ...
const [newTaskTitle, setNewTaskTitle] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
try {
const response = await fetch('/api/tasks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: newTaskTitle }),
});
if (!response.ok) {
throw new Error('Failed to create task');
}
const createdTask = await response.json();
setTasks((prevTasks) => [...prevTasks, createdTask]); // UIを更新
setNewTaskTitle(''); // フォームをクリア
} catch (err) {
setError(err.message);
}
};
return (
<div>
{/* ... 既存のタスクリスト ... */}
<h2>Add New Task</h2>
<form onSubmit={handleSubmit}>
<input
type="text"
value={newTaskTitle}
onChange={(e) => setNewTaskTitle(e.target.value)}
placeholder="Task title"
required
/>
<button type="submit">Add Task</button>
</form>
</div>
);
}
ポイント:
- API ルートに
POST
メソッドを追加し、リクエストボディからデータを受け取ります。 - クライアントコンポーネントでフォームを作成し、
fetch
API を使ってPOST
リクエストを送信します。 - 成功したら、ステートを更新して UI に新しいタスクを表示します。
3. データの「更新」(Update)
既存のデータを変更する方法です。
考え方:
特定のデータ(IDで識別)を対象に、PUT または PATCH リクエストを送信します。
実装例(特定のタスクを更新するAPI ルート):
JavaScript
// app/api/tasks/[id]/route.js (新しいRoute Handlerファイル)
import { NextResponse } from 'next/server';
import prisma from '@/lib/prisma';
export async function PUT(request, { params }) {
const { id } = params;
const { title } = await request.json(); // 更新するデータ
try {
const updatedTask = await prisma.task.update({
where: { id: parseInt(id) }, // IDでタスクを特定
data: { title },
});
return NextResponse.json(updatedTask);
} catch (error) {
return NextResponse.json({ error: 'Failed to update task' }, { status: 500 });
}
}
JavaScript
// app/tasks/page.jsx (更新ボタンとロジックを追加)
// ... 既存のコードの下に追記 ...
export default function TasksPage() {
// ... 既存のtasks, loading, error state ...
const [editingTaskId, setEditingTaskId] = useState(null);
const [editingTaskTitle, setEditingTaskTitle] = useState('');
const handleEdit = (task) => {
setEditingTaskId(task.id);
setEditingTaskTitle(task.title);
};
const handleUpdate = async (taskId) => {
try {
const response = await fetch(`/api/tasks/${taskId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: editingTaskTitle }),
});
if (!response.ok) {
throw new Error('Failed to update task');
}
const updatedTask = await response.json();
setTasks((prevTasks) =>
prevTasks.map((task) => (task.id === taskId ? updatedTask : task))
); // UIを更新
setEditingTaskId(null); // 編集モードを終了
setEditingTaskTitle('');
} catch (err) {
setError(err.message);
}
};
return (
<div>
<h1>My Tasks</h1>
<ul>
{tasks.map((task) => (
<li key={task.id}>
{editingTaskId === task.id ? (
<>
<input
type="text"
value={editingTaskTitle}
onChange={(e) => setEditingTaskTitle(e.target.value)}
/>
<button onClick={() => handleUpdate(task.id)}>Save</button>
<button onClick={() => setEditingTaskId(null)}>Cancel</button>
</>
) : (
<>
{task.title}
<button onClick={() => handleEdit(task)}>Edit</button>
</>
)}
</li>
))}
</ul>
{/* ... Add New Task フォーム ... */}
</div>
);
}
ポイント:
- 動的なルートセグメント
[id]
を使って特定のタスクを対象にする API ルートを作成。 PUT
メソッドで更新データを送信。- UI では、編集ボタンを押すと入力フィールドが表示され、保存ボタンで更新を実行します。
4. データの「削除」(Delete)
不要なデータを消去する方法です。
考え方:
特定のデータ(IDで識別)を対象に、DELETE リクエストを送信します。
実装例(特定のタスクを削除するAPI ルート):
JavaScript
// app/api/tasks/[id]/route.js (DELETEメソッドを追加)
// ... PUTメソッドの下に追記 ...
export async function DELETE(request, { params }) {
const { id } = params;
try {
await prisma.task.delete({
where: { id: parseInt(id) },
});
return NextResponse.json({ message: 'Task deleted successfully' }, { status: 200 });
} catch (error) {
return NextResponse.json({ error: 'Failed to delete task' }, { status: 500 });
}
}
JavaScript
// app/tasks/page.jsx (削除ボタンとロジックを追加)
// ... 既存のコードの下に追記 ...
export default function TasksPage() {
// ... 既存のtasks, loading, error state ...
const handleDelete = async (taskId) => {
try {
const response = await fetch(`/api/tasks/${taskId}`, {
method: 'DELETE',
});
if (!response.ok) {
throw new Error('Failed to delete task');
}
setTasks((prevTasks) => prevTasks.filter((task) => task.id !== taskId)); // UIから削除
} catch (err) {
setError(err.message);
}
};
return (
<div>
<h1>My Tasks</h1>
<ul>
{tasks.map((task) => (
<li key={task.id}>
{/* ... 編集UIまたはタスク表示 ... */}
{editingTaskId !== task.id && ( // 編集中でない場合のみ削除ボタンを表示
<button onClick={() => handleDelete(task.id)}>Delete</button>
)}
</li>
))}
</ul>
{/* ... Add New Task フォーム ... */}
</div>
);
}
ポイント:
- API ルートに
DELETE
メソッドを追加し、URL パラメータの ID を使ってタスクを削除。 fetch
API でDELETE
リクエストを送信し、成功したら UI から該当タスクを削除します。
まとめ
このブログ記事では、Next.js での CRUD 操作の基本的な実装方法を解説しました。
- Read: API ルートでデータを取得し、コンポーネントで表示。
- Create: フォームからデータを入力し、API ルートに
POST
。 - Update: 特定のデータを対象に、API ルートに
PUT
またはPATCH
。 - Delete: 特定のデータを対象に、API ルートに
DELETE
。