自分のアプリを実装しよう
このページで学ぶこと
- 設計が終わった後、どこから実装を始めればいいかわかる
- 機能を小さく分けて進める方法がわかる
- 詰まったときの対処法がわかる
実装の進め方
「設計が終わった。さあ実装しよう!」……でも、何から始めればいいか迷いますよね。
ここでは「読書記録アプリ」を例に、実装を進める順番を説明します。
基本的な順番
「バックエンドを全部作ってからフロントエンドを作る」よりも、
1機能ずつ縦に貫通させる(APIも画面も一緒に作る)方が動く部分が増えてモチベーションが続きます。
ステップ1: プロジェクトをセットアップする
why_react.md でセットアップした my-app プロジェクトをそのまま使うか、
新しいプロジェクトを作ります。
npx create-next-app@latest 自分のアプリ名
cd 自分のアプリ名
npm install @supabase/supabase-js @prisma/client prisma @mui/material @mui/icons-material @emotion/react @emotion/styled.env ファイルを作って、Supabase の接続情報を設定しておきましょう(supabase_setup.md を参照)。
ステップ2: DBを作る
設計したスキーマを prisma/schema.prisma に書いて、マイグレーションします。
npx prisma migrate dev --name initSupabase の「Table Editor」でテーブルが作成されていれば成功です。
ステップ3: 最初の機能を1つ完成させる
最初から全機能を作ろうとしないでください。
「一覧を表示する」という1機能を、APIから画面まで完全に動かすことを目標にしましょう。
例: 読書記録の一覧を表示する
まずAPIを作る
// app/api/books/route.js
import { prisma } from '@/lib/prisma'
export async function GET() {
const books = await prisma.book.findMany({
orderBy: { createdAt: 'desc' },
})
return Response.json(books)
}ブラウザでAPIを確認する
http://localhost:3000/api/books[] (空の配列)が返ってくれば成功。次に画面を作ります。
画面を作る
// app/books/page.js
'use client'
import { useEffect, useState } from 'react'
export default function BooksPage() {
const [books, setBooks] = useState([])
useEffect(() => {
fetch('/api/books')
.then((res) => res.json())
.then((data) => setBooks(data))
}, [])
return (
<main>
<h1>読書記録</h1>
{books.length === 0 && <p>まだ本が登録されていません。</p>}
{books.map((book) => (
<div key={book.id}>
<h2>{book.title}</h2>
<p>{book.author}</p>
</div>
))}
</main>
)
}http://localhost:3000/books を開いて「まだ本が登録されていません。」と表示されれば成功。
ステップ4: 登録機能を追加する
一覧が表示できたら、次は「追加できるようにする」です。
APIを追加する
// app/api/books/route.js に追加
export async function POST(request) {
const body = await request.json()
const book = await prisma.book.create({
data: {
title: body.title,
author: body.author,
memo: body.memo,
},
})
return Response.json(book, { status: 201 })
}フォームを作る
// app/books/new/page.js
'use client'
import { useState } from 'react'
import { useRouter } from 'next/navigation'
export default function NewBookPage() {
const router = useRouter()
const [title, setTitle] = useState('')
const [author, setAuthor] = useState('')
const handleSubmit = async (e) => {
e.preventDefault()
await fetch('/api/books', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title, author }),
})
router.push('/books') // 登録後に一覧へ戻る
}
return (
<main>
<h1>本を登録する</h1>
<form onSubmit={handleSubmit}>
<input
placeholder="タイトル"
value={title}
onChange={(e) => setTitle(e.target.value)}
required
/>
<input
placeholder="著者"
value={author}
onChange={(e) => setAuthor(e.target.value)}
/>
<button type="submit">登録する</button>
</form>
</main>
)
}ステップ5: 削除を追加する
// app/api/books/[id]/route.js
import { prisma } from '@/lib/prisma'
export async function DELETE(request, { params }) {
const { id } = await params
await prisma.book.delete({ where: { id: Number(id) } })
return new Response(null, { status: 204 })
}一覧ページに削除ボタンを追加する。
const handleDelete = async (id) => {
await fetch(`/api/books/${id}`, { method: 'DELETE' })
setBooks(books.filter((b) => b.id !== id))
}
// JSXの中に追加
<button onClick={() => handleDelete(book.id)}>削除</button>ステップ6: 認証を追加する
CRUD が動いたら auth.md を参考に Supabase Auth を組み込みます。
追加する主な手順
- ログイン・サインアップページを作る
- ログアウトボタンを追加する
- ミドルウェアで「ログインしていないと
/booksにアクセスできない」ようにする - 本の
userIdにログイン中のユーザーのIDをセットする(自分のデータだけ表示されるように)
// 本を登録するAPIにユーザーIDを追加する例
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
export async function POST(request) {
const cookieStore = await cookies()
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
{ cookies: { getAll: () => cookieStore.getAll() } }
)
const { data: { user } } = await supabase.auth.getUser()
const body = await request.json()
const book = await prisma.book.create({
data: {
title: body.title,
author: body.author,
userId: user.id, // ログインユーザーのID
},
})
return Response.json(book, { status: 201 })
}詰まったときの対処法
まずこれを確認する
- コンソールを見る — ブラウザの
F12→ Console タブにエラーが出ていないか - ターミナルを見る —
npm run devを実行しているターミナルにエラーが出ていないか - APIを単体で確認する —
http://localhost:3000/api/xxxに直接アクセスしてエラーがないか
AIに聞くテンプレート
【状況】
読書記録アプリで○○を実装しようとしています。
【やったこと】
・app/api/books/route.js に POST を実装した
・app/books/new/page.js にフォームを作った
【エラー】
(エラーメッセージをそのまま貼る)
【コード】
(関係しそうなコードを貼る)
【聞きたいこと】
原因と修正方法を教えてください。30分ルール
30分悩んでも解決しなかったら、すぐメンターに相談してください。
「詰まっている」と報告すること自体がエンジニアの大事なスキルです。
実装の進め方まとめ
| フェーズ | やること |
|---|---|
| ① | プロジェクト・DB作成 |
| ② | 一覧表示(API → 画面)を動かす |
| ③ | 登録機能を追加する |
| ④ | 削除・編集を追加する |
| ⑤ | 認証を組み込む |
| ⑥ | デザインを整える(MUI) |
| ⑦ | デプロイする |
「全部できてから動かす」ではなく、小さく動かしながら積み上げるのがコツです。
確認しよう
- API(GET)を作ってブラウザで確認した
- 一覧画面を作ってAPIとつなげた
- 登録フォームを作って、登録後に一覧に追加されるようになった
- 削除ができるようになった
AIに聞いてみよう
「Next.js 16 の App Router で、フォームのバリデーション(必須チェック、文字数制限)を実装する方法を教えてください」