第5章 APIって何? — データの出し入れ窓口を「設計」する

📖 この章のゴール:API(窓口)とは何か、操作の基本形 CRUD、そして 「同じ窓口でも、人によって返る中身が違う(出し分け)」 の正体が分かる。 ← 目次・はじめにへもどる


📱 Twitterではこう見える

  • 同じ「ホーム」を開いても、人によって出てくる投稿が違う
  • できる操作は決まっている(投稿する・消す・フォローする…)。「好きにDBを書き換える」ことはできない。
  • これらは全部、決まった窓口(API)を通して行われています。

第1章で「API=注文窓口」と説明しました。この章では、その窓口を自分で考える(設計する)目線に立ちます。


🤔 APIって何だっけ?(🟢 基礎)

第1章のおさらい+もう一歩。

API = ブラウザとサーバー(DB)をつなぐ「決まった注文窓口」であり、「できる注文の一覧(メニュー)」でもあります。

レストランで、お客さんはメニューに載っている注文しかできません。厨房(DB)に勝手に入って料理することはできない。これは不便なのではなく、安全と整理のための仕組みです。

だから——

「APIを設計する」=「どんな注文(操作)を受け付けるか、メニューを決める」こと。

メニューに無い操作はできない。これが「なんでもアリ」を防ぎ、アプリを安全に保ちます。


🤔 操作の基本は4つ「CRUD」(🟢 基礎)

データに対してできることは、つきつめると たった4つです。

やりたいこと 呼び方 SQL/操作 Twitterでの例
作る Create insert ツイートする
読む Read select タイムラインを見る
直す Update update プロフィールを編集する
消す Delete delete ツイートを削除する

頭文字を取って CRUD(クラッド) と呼びます。どんなアプリも、だいたいこの4つの組み合わせでできています。

🔁 思い出してください。第3章で insert(作る)と select(読む)をやりました。第4章のRLSでは、この4操作のそれぞれにルールselect / insert / update / delete ポリシー)をかけましたね。CRUDは、第4章で守った4つの操作そのものです。


🤔 「ユーザーごとに出し分ける」の正体(🟢 → 🔧)

ここが、この章のいちばん面白いところです。

AさんとBさんが、まったく同じ「自分のツイート一覧」窓口を叩く。 なのに、Aさんには Aさんの投稿、Bさんには Bさんの投稿が返る。

なぜ、同じ窓口なのに中身が違うのでしょう?

答えは——「誰が叩いたか」が違うからです。

Aさんの注文 ─┐                        ┌─→ Aさんのデータが返る
             ├─→ 同じAPI(窓口)─→ RLS ┤
Bさんの注文 ─┘   +「誰が叩いたか」    └─→ Bさんのデータが返る
  • 窓口(API)は1つ
  • でもリクエストには 「いまログインしている人のID」(=第2章のトークン) がくっついている。
  • DB側の RLS(第4章) が、そのIDを見て 「あなたの行だけ」 を返す。

だから「出し分け」の正体は = 「同じAPI」+「ログイン中の人のID」+「権限ルール(RLS)」。 一覧APIに毎回「user_id = 自分」と書かなくても、ログインIDから自動で出し分けられる——これは第4章「DB側で守る強さ」の言いかえです。


🛠 Supabaseでこう作る(🟢 基礎)

うれしい事実から。

Supabaseは、テーブルを作ると “窓口(API)” を自動で用意してくれます。 あなたがAPIをイチから書く必要はありません。

APIには「2つの顔」がある(🔧)

  1. Supabaseクライアント(書きやすい顔) … JSから supabase.from('tweets').select() のように書く。第3章で使ったのがこれ。
  2. その下で動く REST API(HTTPの素の顔) … クライアントは、この「素のHTTP」への言いやすい言い方にすぎません。

裏側では、だいたいこんなHTTPリクエストが飛んでいます(イメージ)。

GET https://<あなたのプロジェクト>.supabase.co/rest/v1/tweets?order=created_at.desc
Authorization: Bearer <あなたのログイン・トークン>   ← これで「誰が叩いたか」が伝わる
  • GET .../tweets … 「tweets を読む」窓口(エンドポイント)。
  • ?order=created_at.desc … 「新しい順で」という注文の指定。
  • Authorization: Bearer <トークン>第2章のログイン証明書。これがあるから、サーバーは「誰か」を知り、RLSが効いて出し分けられるのです。

CRUDをSupabaseクライアントで

// Create(作る):投稿する
await supabase.from('tweets').insert({ body: 'やあ' });

// Read(読む):新しい順で一覧
await supabase.from('tweets').select('*').order('created_at', { ascending: false });

// Update(直す):このidのツイートを直す
await supabase.from('tweets').update({ body: 'なおした' }).eq('id', 123);

// Delete(消す):このidのツイートを消す
await supabase.from('tweets').delete().eq('id', 123);

1行ずつのポイント:

  • .insert({ ... }) … 1行 追加(Create)。
  • .select('*') … 取り出す(Read)。* は全部の列。
  • .update({ ... }).eq('id', 123) … 「id が 123 の行」を直す(Update)。.eq は「=(イコール)」で対象をしぼる。
  • .delete().eq('id', 123) … 「id が 123 の行」を消す(Delete)。

🔒 ここで第4章が効きます。たとえ 他人の id を指定して updatedelete を叩いても、RLSが「自分の行じゃない」と弾きます。つまり「出し分け(自分の分だけ)」と「安全(他人を触れない)」が、同じRLSの仕組みで同時に実現しています。


⚠️ ハマりどころ

  • 「APIを全部自分で書かなきゃ」と思う … Supabaseが自動で用意します。まずはクライアントから使う。
  • 出し分けをフロントの if で頑張る … 「自分のじゃないから隠す」を画面側だけでやると、窓口を直接叩かれたら漏れます。出し分けと安全は RLS(サーバー側) で。画面の if は見た目専用。
  • トークンを付けずに(匿名で)叩く … 「誰か」が伝わらず、公開データしか取れない/何も取れない。ログイン状態を確認。
  • なんでもできる窓口にしてしまう … メニュー(操作)は必要な分だけ。権限が広すぎるAPIは事故のもと(最小権限)。

🤖 AIに頼むなら(Vibe codingのコツ)

🗣 プロンプト例: 「tweetsCRUD(作る・読む・直す・消す) をSupabaseクライアントで書いて。出し分け(自分の投稿だけ・他人のは触れない)はRLSでやり、フロントの if には頼らないで。読みは新しい順で」

出てきたコードのチェックポイント:

  1. updatedelete持ち主チェック(RLS) で守られている?(フロントの if だけになっていないか)
  2. クライアントが ログイン状態で叩いている?(匿名になっていないか)
  3. 受け付ける操作が必要な分だけに絞られている?

📝 ことばメモ

  • API:決まった注文窓口(メニュー)。外から使える操作の入口
  • CRUD:Create / Read / Update / Delete = 作る・読む・直す・消す
  • エンドポイント:窓口ひとつひとつ(URL)。例 /rest/v1/tweets
  • REST:HTTPでデータを操作する、定番の窓口の作り方
  • 出し分け:同じ窓口でも、ログインしている人によって返る中身が違うこと(正体 = ID + RLS)
  • Bearer トークン:「私はこの人です」とリクエストに添える証明書(第2章のログイン証明書)

➡️ 次の章へ

ここまでは「自分のものは自分だけ」でした。次の第6章では、「公開ツイートは “みんな” に見せる」というルールをRLSに足して、公開タイムライン(全員の投稿が新しい順に流れる画面)を作ります。「自分だけ」と「みんな」を、どう両立させるかがテーマです。

← 目次・はじめにへもどる