【バイブコーディング_No001】AIと一緒にTodoアプリを作って学んだこと — CRUD・認証・RLSを完全理解するまで

はじめに

「バイブコーディング」で、Next.js × Supabase のTodoアプリを作りました。

「Todoアプリなんて地味じゃない?」と思うかもしれませんが、ここで学ぶ CRUD操作・認証・RLS(行レベルセキュリティ) は、SNSもECサイトもSaaSも、あらゆるWebアプリの土台になる超重要パターンです。

この記事では、AIにコードを書いてもらいながら「なぜそうなるのか」を自分で理解していく過程をまとめます。これからバイブコーディングでアプリ開発を始める方の参考になれば嬉しいです。


使った技術スタック

道具 役割 Next.js(App Router) 画面を作るフレームワーク Supabase データベース+ログイン機能 Tailwind CSS 見た目をきれいにするCSS shadcn/ui ボタンやチェックボックスなどのUIパーツ Vercel デプロイ(公開)先


作る前に理解した「3つの概念」

コードを書く前に、まずこの3つをしっかり理解しました。AIにコードを任せるにしても、「なぜそうなっているか」がわからないと詰みます。

概念① CRUD(クラッド)

すべてのアプリは、データに対して たった4つのこと しかしていません。

📒 やることノートで考えると…

1. 「牛乳を買う」と書く     → Create(作る)
2. ノートを開いて見る       → Read(読む)
3. 「牛乳」を「豆乳」に直す → Update(直す)
4. 終わったので消す         → Delete(消す)

SNSの「投稿する・見る・編集する・消す」もCRUD。ECサイトの「商品登録・一覧表示・価格変更・出品取り下げ」もCRUD。この4パターンを体に染み込ませれば、どんなアプリも分解して考えられるようになります。

概念② 認証(ログイン)

ログインがないと全員のTodoが丸見えになってしまいます。認証は「あなたは誰?」を確認する仕組みです。

ホテルのカードキーで考えるとわかりやすいです:

① チェックイン(=ログイン)
   → 本人確認 → カードキーを渡す

② カードキー(=JWT トークン)
   → 自分の部屋だけ開けられる
   → チェックアウトまで何度でも使える

③ チェックアウト(=ログアウト)
   → カードキー返却 → もう入れない

Webアプリでは、この「カードキー」にあたるものが JWT(JSON Web Token) という通行証で、ブラウザの Cookie に自動保存されます。ページを開くたびに自動で通行証を提示してくれるので、ユーザーは意識する必要がありません。

概念③ RLS(Row Level Security)

認証だけでは実は不十分。ログインで「田中さんだ」とわかっても、悪意あるリクエストで「佐藤さんのデータもよこせ」と送られたら?

ここで RLS(行レベルセキュリティ)が活躍します:

🏢 マンションの郵便受けのイメージ

認証(ログイン)= マンションのオートロック
  → 住人なら建物に入れる

RLS = 各部屋の郵便受けの鍵
  → 自分の部屋の郵便しか取れない
  → たとえ建物に入れても、他人のものは絶対に開かない

RLSはデータベース側に設定する壁なので、アプリのコードを改造されても突破できません。認証とRLSの「二重のカギ」で初めて安全になります。


開発の全体像と進め方

「なぜこの順番で作るのか?」を先に理解してから進めました。建物を建てるのと同じで、下の階がないと上の階は建てられません。

Step 01  Supabase セットアップ    → 倉庫を用意
Step 02  Supabase 接続設定        → 倉庫への道をつなぐ
Step 03  認証ミドルウェア          → 門番を立てる
Step 04  ログイン画面              → 入口を作る
Step 05  Todo 追加 (Create)       →┐
Step 06  Todo 一覧 (Read)         →┤ CRUDを1つずつ
Step 07  Todo 完了切替 (Update)   →┤
Step 08  Todo 削除 (Delete)       →┘
Step 09  UI 仕上げ                → 見た目を整える
Step 10  デプロイ                  → 世界に公開

Step 01〜02:倉庫を用意して道をつなぐ

やったこと

  • Supabaseでプロジェクト作成、todosテーブルとRLSポリシーを設定

  • アプリとSupabaseをつなぐ接続ファイル(client.ts / server.ts)を作成

学んだこと:なぜ接続ファイルが2つ必要なのか

ここが最初につまずきやすいポイントでした。

時系列で見ると…

① あなたがURLを開く
      ↓
② 【server.ts】サーバーがデータを取得してページを作る
      ↓
③ 出来上がったページがブラウザに届く
      ↓
④ あなたがボタンを押す
      ↓
⑤ 【client.ts】ブラウザから直接Supabaseにアクセス
      ↓
⑥ 画面が更新される

ポイントは ②と⑤が別のコンピュータで動いている ということ。

  • server.ts → ページを「作る」ときに使う(サーバー側。Cookieを手動で扱う必要がある)

  • client.ts → ページを「操作する」ときに使う(ブラウザ側。Cookieは自動)

ブラウザにはCookieを自動で扱う機能があるけど、サーバーにはそれがない。だからCookieの扱い方が違い、ファイルを分ける必要がある。

学んだこと:環境変数(.env.local)

Supabaseの接続情報(URLとanon key)はコードに直接書かず、.env.local ファイルに分離します。

❌ コードに直接書く → GitHubにpushしたら世界中に公開される
✅ .env.local に書く → .gitignoreで除外されるのでGitHubにはアップされない

NEXT_PUBLIC_ が付いた変数はブラウザからも読める。anon keyは「公開鍵」なのでブラウザに渡しても安全(RLSが守ってくれるから)。


Step 03:門番を立てる(Middleware)

やったこと

  • ログインしていない人をTodo画面に入れないようにするMiddlewareを設置

学んだこと:壁を先に作ってからドアを付ける

直感的には「ログイン画面を先に作りたい」と思うけど、順番が逆。

門番がいない状態:
  誰か → / にアクセス → Todo画面が見えてしまう(ログインしてないのに!)

門番を設置した後:
  誰か → / にアクセス
    → 門番「通行証を見せて」
    → 持ってない → /login にリダイレクト
    → 持ってる  → どうぞお入りください

壁がなければドアの意味がない。 だから門番を先に作ります。

学んだこと:Middlewareの特別な性質

Next.jsでは middleware.ts という名前のファイルは特別扱い。自分で「呼び出す」コードを書かなくても、すべてのアクセスの前に自動で実行されます。置くだけで動く門番。


Step 04:入口を作る(ログイン画面)

やったこと

  • ログイン画面(/login)を作成

  • 新規登録・ログインの両方に対応するAuthFormコンポーネントを作成

学んだこと:データの流れ

メール + パスワード入力 → 「ログイン」ボタン
  → Supabase がパスワードを照合
  → OKなら通行証(JWT)を発行 → Cookieに保存
  → / にリダイレクト
  → 門番「通行証ありますね、どうぞ」
  → Todo画面が見える!

この時点で初めて**「人がアプリに入れる状態」**になりました。

学んだこと:page.tsxとコンポーネントの分離

  • page.tsx → 「/loginというURLにこの画面を表示する」という設定(枠)

  • AuthForm.tsx → 入力欄とボタンの見た目+動作(中身の部品)

将来デザインを変えたいとき、AuthFormだけ触ればOK。ページ構造を壊さずに済む。


Step 05:Todo追加(Create)

やったこと

  • デフォルトのNext.js画面をTodo画面に差し替え

  • ログアウトボタンを追加

  • AddTodoコンポーネント(入力欄 + 追加ボタン)を作成

学んだこと:配管工事を先にやる

追加してもまだ画面には表示されない(一覧表示は次のStep)。「本当に保存できたか」はSupabase Dashboardで確認。

これは配管工事のようなもの。水を流す仕組み(データ保存)を先に作って、蛇口(一覧表示)は後から付ける。

「牛乳を買う」と入力 → 「追加」ボタン
  → client.tsでSupabaseに送信
  → RLSチェック「user_idがログイン中の人と一致?→ OK」
  → DBに1行追加
  → 成功 → 入力欄をクリア

Step 06:Todo一覧表示(Read)

やったこと

  • TodoListコンポーネント(DBからTodoを取得してリスト表示)

  • TodoItemコンポーネント(1件分の表示:タイトル+チェックボックス+削除ボタン)

学んだこと:コンポーネントの責任分担

TodoList = ホール係(注文をまとめて厨房に伝える)
  → DBからデータを取得し、1件ずつTodoItemに渡す

TodoItem = お皿(1品ずつ盛り付けて出す)
  → チェックボックス、タイトル、削除ボタンを表示

1件の表示と全体の管理は責任が違うのでファイルを分ける。後で機能追加するときに1つのファイルが巨大にならずに済む。

この時点でチェックボックスと削除ボタンは見た目だけ。クリックしても何も起きません。


Step 07:Todo完了切替(Update)

やったこと

  • TodoItemのチェックボックスにクリックイベントを追加

学んだこと:変更量の少ないStep

1ファイルの一部を変えるだけ。CRUDの中でもUpdateはシンプルでした。

チェックボックスをクリック
  → 「この Todo の is_complete を反転して」
  → RLSチェック「持ち主は自分?→ OK」
  → 完了状態が切り替わる + 取り消し線が付く

Step 08:Todo削除(Delete)

やったこと

  • TodoItemに削除クリックイベントを追加

  • TodoListに削除後のリスト更新処理を追加

学んだこと:なぜ削除はTodoListも変更が必要なのか

完了切替(Update)ではTodoItem内で完結した。でも削除は行そのものが消えるので、親コンポーネントに伝える必要がある。

完了切替 = お皿の料理に「食べました」の旗を立てる → お皿だけで済む
削除     = お皿ごと下げる → ホール係に「下げて」と伝える必要がある

これでCRUDが全部揃いました!

C(Create) ✅ 追加
R(Read)   ✅ 一覧表示
U(Update) ✅ 完了切替
D(Delete) ✅ 削除

Step 09:UI仕上げ

やったこと

  • タイトルを「Todo App」に変更

  • ホバー効果、完了済みの見た目改善

  • ログイン画面のデザイン統一

  • レスポンシブ対応

学んだこと:見た目は最後に整える

「見た目から作りたい」気持ちはあるけど、先にやると効率が悪い。

❌ 先にUIを整える → 機能追加のたびにレイアウトが崩れて何度も直すことに
✅ 機能が全部動いてからUI → 1回で全体を仕上げられる

建築で言えば、内装工事は骨組みと配管が終わってから。機能は一切変えず、CSSクラスだけ調整しました。


全体を通して学んだ最も大事なこと

1. 「依存するもの」を先に、「依存されるもの」を後に

倉庫がなければ道はつなげない。壁がなければドアの意味がない。データがなければ一覧も更新も削除もテストできない。1つ作るたびに確認して、壊れたらすぐ気づけるようにする。 この考え方はTodoアプリに限らず、あらゆる開発に共通します。

2. CRUDを1つずつ作る理由

一気に全部作ると、どこでバグが出たか切り分けできない。1つずつ作れば、問題があってもその1機能だけ調べればいい。

3. コードはAIに任せても「なぜ」は自分で理解する

バイブコーディングの醍醐味は、AIにコードを書いてもらいながら「なぜこの構造なのか」「なぜこの順番なのか」を対話的に学べること。コードを暗記する必要はないけど、データの流れとセキュリティの仕組みは自分の言葉で説明できる状態を目指しました。


ファイル構成(完成形)

todo-app/
├── src/
│   ├── app/
│   │   ├── layout.tsx        ← 全画面共通の枠
│   │   ├── page.tsx          ← Todoリスト画面(メイン)
│   │   └── login/
│   │       └── page.tsx      ← ログイン画面
│   ├── components/
│   │   ├── ui/               ← shadcn/uiの部品
│   │   ├── AuthForm.tsx      ← ログインフォーム
│   │   ├── AddTodo.tsx       ← Todo追加フォーム
│   │   ├── TodoList.tsx      ← Todoリスト全体
│   │   └── TodoItem.tsx      ← Todo1件の表示
│   ├── lib/
│   │   └── supabase/
│   │       ├── client.ts     ← ブラウザ用の接続
│   │       └── server.ts     ← サーバー用の接続
│   └── middleware.ts         ← 門番(認証チェック)
├── .env.local                ← 接続情報(Git管理外)
└── package.json

ハマったポイント & 解決策

症状 原因 解決策 画面が真っ白 環境変数が未設定 .env.local の内容を確認 ログインできない Supabaseのメール確認が有効 Dashboard → Authentication → 「Confirm email」をオフに(学習用) Todoが表示されない RLSポリシーが未設定 SQL Editorでポリシーを再実行 Todoが追加できない user_idを渡していない insertに user_id を含めているか確認 デプロイ後に動かない Vercelに環境変数が未設定 Vercel Dashboard → Settings → Environment Variables


覚えた用語リスト

用語 ひとことで CRUD データ操作の4パターン(Create / Read / Update / Delete) JWT ログイン後に発行される「通行証」。Cookieに保管される Cookie ブラウザの保管場所。JWTを自動で送信してくれる RLS データベース側の壁。自分のデータだけ見せる仕組み Middleware すべてのアクセスの前にチェックする門番 環境変数 秘密情報をコードと分離して管理する仕組み Server Component サーバーで動く。ページを「作る」段階で使う Client Component ブラウザで動く。ユーザー操作(クリック等)を扱う


次にやること

Todoアプリで学んだ CRUD + 認証 + RLS は、今後のアプリ開発すべての土台になります。

No.1  Todoアプリ        ← 今回。CRUD・認証・RLSの基本
  ↓
No.2  UIコンポーネント写経 → 見た目の部品をもっと学ぶ
  ↓
No.5  家計簿アプリ       → CRUDに「集計」を追加
  ↓
No.8  習慣トラッカー     → CRUDに「日付処理」を追加

焦らず、しっかり土台を固めていきます。


バイブコーディングは「AIに書いてもらっておしまい」ではなく、「AIと対話しながら、なぜそうなるかを理解していくプロセス」。コードは忘れても、仕組みの理解は残る。

いいなと思ったら応援しよう!