CRUD操作を実装しよう
このページで学ぶこと
- CRUD(作成・取得・更新・削除)の全操作を実装できる
- フロントエンドとバックエンドを組み合わせた Todoアプリが作れる
CRUD とは
| 操作 | 英語 | HTTP メソッド | Prisma |
|---|---|---|---|
| 作成 | Create | POST | create |
| 取得 | Read | GET | findMany / findUnique |
| 更新 | Update | PUT / PATCH | update |
| 削除 | Delete | DELETE | delete |
Todoアプリ全機能を実装する
バックエンド(API Routes)
一覧取得・作成: app/api/todos/route.js
import { NextResponse } from 'next/server'
import { prisma } from '../../../lib/prisma'
// GET /api/todos — 全件取得
export async function GET() {
const todos = await prisma.todo.findMany({
orderBy: { createdAt: 'desc' }
})
return NextResponse.json(todos)
}
// POST /api/todos — 新規作成
export async function POST(request) {
const { title } = await request.json()
if (!title?.trim()) {
return NextResponse.json({ error: 'titleは必須です' }, { status: 400 })
}
const todo = await prisma.todo.create({
data: { title: title.trim(), userId: 1 } // ← 今は仮のID。認証を実装したら本物のユーザーIDに差し替える
})
return NextResponse.json(todo, { status: 201 })
}更新・削除: app/api/todos/[id]/route.js
import { NextResponse } from 'next/server'
import { prisma } from '../../../../lib/prisma'
// PATCH /api/todos/:id — 完了状態を切り替え
export async function PATCH(request, { params }) {
const { id } = await params // Next.js 16 では await が必要
const { completed } = await request.json()
const todo = await prisma.todo.update({
where: { id: parseInt(id) },
data: { completed }
})
return NextResponse.json(todo)
}
// DELETE /api/todos/:id — 削除
export async function DELETE(request, { params }) {
const { id } = await params // Next.js 16 では await が必要
await prisma.todo.delete({ where: { id: parseInt(id) } })
return NextResponse.json({ message: '削除しました' })
}フロントエンド: app/page.js
'use client'
import { useState, useEffect } from 'react'
export default function TodoApp() {
const [todos, setTodos] = useState([])
const [input, setInput] = useState('')
const [loading, setLoading] = useState(true)
// 一覧取得
useEffect(() => {
fetch('/api/todos')
.then((res) => res.json())
.then((data) => {
setTodos(data)
setLoading(false)
})
}, [])
// 追加
const addTodo = async () => {
if (!input.trim()) return
const res = await fetch('/api/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: input }),
})
const newTodo = await res.json()
setTodos([newTodo, ...todos])
setInput('')
}
// 完了状態の切り替え
const toggleTodo = async (id, completed) => {
const res = await fetch(`/api/todos/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ completed: !completed }),
})
const updatedTodo = await res.json()
setTodos(todos.map((t) => (t.id === id ? updatedTodo : t)))
}
// 削除
const deleteTodo = async (id) => {
await fetch(`/api/todos/${id}`, { method: 'DELETE' })
setTodos(todos.filter((t) => t.id !== id))
}
if (loading) return <p>読み込み中...</p>
return (
<div style={{ padding: '32px', maxWidth: '500px' }}>
<h1>Todoアプリ</h1>
{/* 入力フォーム */}
<div style={{ display: 'flex', gap: '8px', marginBottom: '16px' }}>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && addTodo()}
placeholder="新しいTodoを入力"
style={{ flex: 1, padding: '8px' }}
/>
<button onClick={addTodo}>追加</button>
</div>
{/* Todoリスト */}
<ul style={{ listStyle: 'none', padding: 0 }}>
{todos.map((todo) => (
<li
key={todo.id}
style={{
display: 'flex',
alignItems: 'center',
gap: '8px',
padding: '8px 0',
borderBottom: '1px solid #eee',
}}
>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id, todo.completed)}
/>
<span style={{ flex: 1, textDecoration: todo.completed ? 'line-through' : 'none' }}>
{todo.title}
</span>
<button onClick={() => deleteTodo(todo.id)}>削除</button>
</li>
))}
</ul>
{todos.length === 0 && <p>Todoがありません。追加してみましょう!</p>}
</div>
)
}動作確認
npm run devでサーバーを起動するhttp://localhost:3000にアクセスする- 以下を試す
- Todoを入力して「追加」を押す → DBに保存される
- チェックボックスをクリックする → 完了状態が切り替わる
- 「削除」を押す → DBから削除される
- ページをリロードする → データが消えないことを確認する
確認しよう
- Todo の追加・取得・更新・削除がすべて動作した
- ページをリロードしてもデータが保存されていた(DBに永続化)
- Supabase ダッシュボードの Table Editor でデータが増減するのを確認した
ここまでで何ができた?
おめでとうございます!ここまでで以下が揃っています:
- データをDBに保存できる(Supabase + Prisma)
- APIでデータを取得・作成・更新・削除できる
- フロントエンドからAPIを呼んで、画面に反映できる
これは Webアプリの骨格 です。あとは認証を足して「誰のデータか」を管理できるようにすれば、本格的なアプリになります。
このTodoアプリをさらに発展させるには
現状のTodoアプリには1つ課題があります。userId: 1 と固定しているため、
誰がアクセスしても同じデータが見える状態です。
認証を追加すると:
- ユーザーごとにログイン・サインアップできる
- 自分のTodoだけが表示される(
userIdに実際のログインユーザーのIDが入る) - ログアウトするとデータにアクセスできなくなる
次のページのセキュリティ(SQLインジェクション対策)を学んだ後、認証に進みます。
AIに聞いてみよう
「PrismaのupsertとcreateOrUpdateの違いを教えてください。どんな場面で使いますか?」
次のステップ
Last updated on