第3章 ツイートしてみる — はじめてのデータ保存
📖 この章のゴール:データを「保存する(書く)」と「取り出す(読む)」という、Webアプリの基本動作を、実際にやってみる。 ← 目次・はじめにへもどる
📱 Twitterではこう見える
文章を書いて「ポスト」を押すと、自分の投稿が一覧の一番上に増えます。 いちど投稿すれば、アプリを閉じても、別の端末で開いても、ちゃんと残っています。
やっていることは、たった2つだけ。
- 保存する(投稿=データを書き込む)
- 取り出す(一覧=データを読み込む)
この章では、この2つを実際に作ります。
🤔 「投稿する」って、何をしているの?(🟢 基礎)
第1章で、データは 倉庫(データベース) に保存される、と説明しました。投稿とは——
倉庫の「表」に、1行 書き足すこと。
「表」は テーブル と呼びます。イメージは、第1章でも出てきた 超巨大な表計算(Excel)の1シートです。
- 列(カラム) … 「項目」。あらかじめ決めておく(例:本文、時刻)
- 行(レコード) … 1件のデータ。1行=1つのツイート
今回作る tweets テーブルは、こんな形にします。
| 列(カラム) | 入るもの | 例 |
|---|---|---|
id |
1件ごとの通し番号(自動) | 1, 2, 3… |
body |
ツイート本文 | はじめてのツイート! |
created_at |
投稿した時刻(自動) | 2026-06-26 12:00 |
📓 たとえ話:ノートに書き足す ノートに1行ずつメモを足していくのと同じです。
idはページ番号、bodyは内容、created_atは書いた時刻。投稿するたびに、新しい1行が増えていきます。
🔖 いまは「誰の投稿か」を入れていないことに注目してください。持ち主(
user_id)は、次の第4章で足します。この章では、まず “保存して読み出す” だけに集中します。
🛠 Supabaseでこう作る(🟢 基礎)
① テーブルを作る
Supabaseの管理画面(SQL Editor)で、次のSQLを実行します。
create table tweets (
id bigint generated always as identity primary key, -- 1件ごとの通し番号(自動)
body text not null, -- ツイート本文(空はダメ)
created_at timestamptz not null default now() -- 投稿した時刻(自動)
);
1行ずつ読むと:
create table tweets (… 「tweetsという名前の表を作る」という宣言。id ... generated always as identity primary key…idは DBが自動で振る通し番号。primary key(主キー)は「行を区別する、重複しない目印」。body text not null… 本文を入れる列。textは文字、not nullは「必ず何か入れる(空はダメ)」。created_at ... default now()… 投稿時刻。default now()は「書いた瞬間の時刻を自動で入れる」。
② 投稿する(1行 追加 = insert)
ブラウザ側から、こうお願いします。
// 「この文章を保存して」とお願いする
const { data, error } = await supabase
.from('tweets')
.insert({ body: 'はじめてのツイート!' })
.select();
1行ずつ読むと:
supabase.from('tweets')… 「tweets表に用がある」と伝える。.insert({ body: '...' })…bodyを入れた 1行を追加して、とお願い。.select()… 追加した結果(idとcreated_at付き)を返してもらう。const { data, error }… 結果はdata(成功した中身)かerror(失敗の理由)で受け取る。
💡
idとcreated_atは書いていないのに入ります。①で「自動」と決めたので、DBが勝手に付けてくれるのです。
③ 一覧を取り出す(読む = select、新しい順)
// 「投稿の一覧を、新しい順でちょうだい」
const { data, error } = await supabase
.from('tweets')
.select('*')
.order('created_at', { ascending: false });
1行ずつ読むと:
.select('*')… 「全部の列をちょうだい」(*は「全部」)。.order('created_at', { ascending: false })… 「created_atで 新しい順に並べて」(ascending: false= 新しいものが上)。
あとは、受け取った data(ツイートの配列)を画面に並べるだけで、タイムラインの原型ができます。
⚠️ ハマりどころ
errorを見ない …insertもselectも失敗することがあります(ネット切れ・設定ミスなど)。if (error) { ... }で必ず確認を。- 「保存したはずなのに表示されない」 … よくある原因は、第4章で出てくる RLS(鍵)の設定や、別アカウントでの確認。いまは1人で試している前提でOK、整理は第4章で。
- 本文をそのまま画面に貼る … 利用者が書いた文字をHTMLへ生のまま出すと、後述の XSS(第10章)の入口に。ふつうは画面ライブラリが自動で安全化してくれますが、「人の入力は要注意」と頭の隅に。
- いまは “持ち主” がない … だから今は全員の投稿が混ざり、誰でも消せる状態。これは欠陥ではなく、第4章でわざと直すための出発点です。
🤖 AIに頼むなら(Vibe codingのコツ)
🗣 プロンプト例: 「Supabaseで
tweetsテーブル(id・body・created_at)を作って。投稿するinsertと、新しい順の一覧selectの最小コードを、error処理つきで書いて。いまはuser_idや RLS はまだ入れないで(次の段階でやる)」
出てきたコードのチェックポイント:
insertのあとに.select()で結果を確認している?errorをちゃんと見ている?- 一覧が
order(...)で新しい順になっている?
📝 ことばメモ
- テーブル(表):データをためる表。列(カラム)と行(レコード)でできている
- カラム(列):
id・body・created_atなどの「項目」 - レコード(行):1件のデータ=1つのツイート
- insert / select:追加する / 取り出す
- 主キー(primary key):行を区別する、重複しない番号(
id)
➡️ 次の章へ
保存と表示ができました。でも、いまの tweets は 「誰の投稿か」が無く、全員のものが丸見え・誰でも消せる状態です。次の 第4章 で、投稿に 持ち主(user_id) を付け、RLS(鍵) をかけて、「自分のものは自分だけ」を実現します。ここが教材の背骨です。