MIXXIN Vibe Coder 入門課
LESSON 05 · 午餐揪團頁主線 · 全課程高潮

接上大家的倉庫:從讀到寫

到今天為止,你頁面上的東西都是「你自己的」——你寫死的清單、你這台電腦記住的跟團紀錄。今天要把它接到一個全班共用的地方,先讀,再寫。寫進去的那一刻,同事的畫面上會出現你的名字。

先看整張架構 讀:接上倉庫 寫:引爆共用 產出:同事看得到你
完成定義 · Definition of Done
你的頁面上的店家清單來自 Notion 上那張共用的店家表(跟同學的數字一樣),而且頁面上有一顆按鈕連得過去;你按下「我要跟」之後,那一票真的寫進 Notion,同事重整他的頁面看得到;你能親口說出「這件事為什麼不能只靠前端完成」。

開始之前 · 30 秒

  • 今天改的還是你上一堂那個檔案——不用新開資料夾(第 3 堂起就是一頁持續演進)
  • 忘記它在哪?直接問 Claude Code:「我的午餐揪團頁是哪一個檔案?」
  • 今天全程看本機那個檔案就好,不用每次都 push 上線。最後交作業時再一起 push
今天要用到的東西(先確認你都打得開):你的午餐揪團頁、Claude Code、GitHub Desktop、瀏覽器的開發者工具、還有 Notion。這些都是你已經用過的,今天沒有新工具要裝。

本堂材料 · 下面的 Prompt 都已經幫你填好了,這裡是備查

① 資料網址https://vibe-course.mixxin.tw/api/lunch
貼到瀏覽器就看得到——這是你的頁面等一下要抓的東西
② 投票網址https://vibe-course.mixxin.tw/api/join
送出的內容:{"name": "店名", "who": "你的名字"}
③ 店家表(Notion)點我開啟 · 餐廳列表
全班共用的餐廳清單,維護按鈕要連到這裡。只有講師改得動
④ 跟團紀錄表(Notion)點我開啟 · 跟團紀錄
你等一下投的每一票都會出現在這裡
⑤ 唯讀金鑰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
只能讀、不能寫,而且只開得了上面那兩張表
那把金鑰為什麼可以大方貼在這裡?因為它只能讀,而且只讀得到午餐這兩張表——就算流出去,別人頂多看到我們中午吃哪家。
這跟等一下第 05 節要講的事情正好是一體兩面:金鑰的價值取決於它能做什麼。一把只能讀午餐清單的鑰匙可以印在講義上;一把能寫入的鑰匙連放進你的頁面都不行。
01先看整張圖:東西到底從哪來

在動手之前先看清楚今天的地形。你的頁面要拿到全班的資料,中間會經過三格——而你今天只動最左邊那一格

① 你的頁面 瀏覽器裡 index.html 你改得到 要資料 回乾淨資料 ② 我們的伺服器 🔑 金鑰藏在這裡 幫你問、幫你整理 你看不到裡面 帶金鑰去問 一大包原始資料 ③ Notion 那張表 全班共用 你打得開、看得到 今天你動 ① ② 講師已經架好 ③ 你用手貼一列進去
圖 1.1今天的全景:你的頁面不直接碰資料庫,中間隔一台幫你保管金鑰的伺服器。三格裡你只動最左邊那一格。
① 你的頁面就是你一直在改的那個 index.html。今天要教它「去別的地方拿資料」
② 我們的伺服器講師已經架好了。它手上有一把鑰匙,負責去 Notion 拿資料、整理乾淨再回給你
③ Notion 那張表全班共用的資料就放在這。你等一下會親手打開它、也會親手寫一筆進去

一定會有人問:為什麼要中間那台?我的頁面不能直接去問 Notion 嗎?自己試一次比較快——

  • 把這個網址貼到瀏覽器網址列按 Enter:https://api.notion.com/v1/users
  • 你會看到一段錯誤訊息,大意是「你沒有帶通行證」——這就是「需要金鑰的資料」長的樣子
而就算你有金鑰,也不能放在頁面裡。你的頁面是公開的,誰都打得開來看——鑰匙放進去就等於掛在門口。
(另外我們也實測過:Notion 根本不接受瀏覽器直接打過去的請求,會被擋下來。)這條界線叫什麼、為什麼存在,本堂稍後第 06 節整段講。
02打開倉庫看:資料庫就長這樣

現在就點開這張表:全班共用的餐廳列表 ↗

你會看到一張表。一列就是一筆紀錄一欄就是一種資料(店名、跟團人數、發起人、日期)。就這樣,沒有更神祕的東西——你每天在 Notion 開的那些表,就是資料庫

  • 點開餐廳列表,確認你打得開
  • 數一下現在總共有幾列——等一下你的頁面上要出現同一個數字
  • 看一下欄位有哪些:店名、跟團人數、發起人……這些等一下會變成你頁面上的卡片
🗂️你已經會用資料庫了

第 4 堂你抓過的那包 JSON,跟這張表其實是同一種東西的兩種樣子——表格是給人看的,JSON 是給程式看的。等一下你會看到同一筆資料的這兩個版本。

03動手一:讀店家表(只讀)

你要做的事:把頁面裡寫死的那份清單,換成「去問我們的伺服器」。

注意這一步只有讀。那張店家表是講師在 Notion 維運的——新增一家店、改地址、把收掉的店下架,都在 Notion 上做。你的頁面只負責把它顯示出來,動不了它。

好消息:今天的 Prompt 裡網址都幫你填好了,整段複製貼上就好,不用自己換東西。唯一一個要你動手改的是等一下投票那段裡的 REPLACE_ME——把它換成你的名字。

起手式 · 用自己的話改

A頁面改成讀店家表
我的頁面目前的地點清單是寫死在程式碼裡的。
請改成從這個網址抓資料:https://vibe-course.mixxin.tw/api/lunch

抓到之後,顯示全班目前湊了幾家店,每一家做成一張卡片(店名、地址、評分)。
有些店沒有評分,那些不要顯示星等,也不要出現 null。
抓不到的時候要顯示「暫時讀不到,等一下再試」,不要整頁壞掉。

另外請在頁面上加一個「維護店家清單」的按鈕,
點了在新分頁打開這個網址:https://app.notion.com/p/3bb95ea1884180a8b215fcb748371e19

重要:請保留我原本的「我要跟」按鈕和它的 localStorage 邏輯,
不要把它們刪掉或重寫——等一下的步驟還要用到那顆按鈕。
  • 貼上 Prompt A,讓 Claude Code 改你的頁面
  • 重新整理頁面——清單變成從店家表來的了
  • 按一下「我要跟」,確認數字還是會加(如果按鈕不見了或按了沒反應,跟 AI 說「我原本的我要跟按鈕不見了,請加回來」)
  • 點那顆「維護店家清單」按鈕,確認它會開到 Notion 那張表
  • 問旁邊的同事:你們畫面上的店家總數一樣嗎?
你會看到的變化:頁面上的店家數字,從「你自己那份」變成跟旁邊同學一模一樣。這就是「大家指向同一份資料」的意思——你們讀的是同一張表。
🔗剛剛那顆按鈕在做一件很重要的事

它把「畫面」跟「資料的家」連起來了。以前你要改店家資料得去改程式碼,現在任何人(包括不會寫程式的人)都能點那顆按鈕、直接在 Notion 上改一格,你的頁面下次重整就跟上。這就是「資料跟程式碼分開」的好處——維運的人不需要動到程式。

卡住了怎麼辦(今天會用好幾次):
① 畫面沒變或變成空白 → 右鍵 → 檢查 → 上面那排找到 Console,紅色的字就是它在抱怨什麼。
② 把整段紅字複製起來,貼給 Claude Code,加一句:「我的頁面出現這個錯誤,請解釋原因並修好它。」
③ 想看資料到底有沒有回來 → 切到 Network 分頁,重整一次,找到那條請求點開看。
這兩個面板第 4 堂都用過,今天是它們真正派上用場的一天。
04撞牆:讀得到,卻寫不進去

現在你的頁面讀得到全班的資料了。所以做個實驗:按下你頁面上的「我要跟」,然後請旁邊的同事重整他的頁面。

他看不到你剛剛跟的團。明明你們讀的是同一張表——為什麼讀得到大家的,自己加的卻進不去?
因為你按下去的那個動作,根本沒有送出去,它只是掉回你自己那台電腦的抽屜(localStorage)裡。讀可以隨便讀、寫卻不行——這個不對稱不是漏做,是接下來三節要解釋的東西。
落單了也能做:沒有同事在旁邊的話,用手機打開你的頁面網址,或在電腦上開一個無痕視窗——效果一樣,那邊看不到你剛跟的團。
兩人各拿一支手機比對畫面、表情困惑的氛圍照
圖 4.1撞牆瞬間:同一張表,你的動作卻進不去
05動手二:金鑰與權限

要理解剛剛那面牆是什麼做的,先親手拿一把鑰匙。這把不是給頁面用的(頁面不能放鑰匙),是給你自己電腦上的 AI 用的,讓它可以直接去問 Notion。

🔑金鑰不是密碼

密碼是「證明你是你」,金鑰比較像一把配好的鑰匙——可以只開特定幾扇門。今天給你的這把只能讀、不能寫,而且只開得了那一個資料庫。等一下你會親眼看到「不能寫」是真的。

先解決一個一定會卡住的地方

Mac 的 Finder 看不到 .env.local 這種檔案。檔名以一個點開頭的檔案,Mac 預設是隱藏的——你在 Finder 裡找不到它,會以為自己沒建成功。
等一下建好之後,請按這個組合鍵確認它真的在:在 Finder 裡按 Command + Shift + 句點,隱藏檔案就會顯示出來,你應該看得到 .env.local 躺在那裡(再按一次收回去)。

起手式 · 用自己的話改

B把金鑰放好,然後去問 Notion
請幫我在這個專案資料夾裡建一個 .env.local 檔案,內容是:
NOTION_TOKEN=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

建好之後,請用那把 token 去查這個 Notion 資料庫,
告訴我現在總共有幾筆紀錄,並把前三筆的店名列出來。
資料庫網址:https://app.notion.com/p/3bb95ea1884180a8b215fcb748371e19

注意:Notion 在 2025-09-03 改過查詢方式,請用 data sources 的端點、
Notion-Version 帶 2025-09-03。如果你記得的是舊的 databases 端點,那是過時的寫法。
最後那兩行是講給 AI 聽的,你不用懂。它的意思是「你腦中記得的做法可能過期了」——AI 學到的知識有時間點,Notion 在 2025 年 9 月改過規則,不提醒它,它會很有自信地給你一段跑不動的東西。
這就是這門課一直在練的:AI 很會寫,但它不一定知道「現在」。
  • 貼上 Prompt B,讓 AI 幫你建檔並查資料
  • AI 回你「現在有 N 筆」——對照一下,跟你剛剛在 Notion 上數的一樣嗎
  • 在 Finder 按 Command + Shift + 句點,親眼看到 .env.local 在那裡
  • 打開 GitHub Desktop,左邊那排「Changes」清單——.env.local 不在上面,因為它被列在一份「永遠不要送出去」的名單裡
你會看到的變化:AI 回給你的是一大包很長、層層包起來的 JSON——那是 Notion 的原始資料。對照一下你頁面上那個乾淨的清單:中間那台伺服器就是在幫你做這件整理工作

現在測試這把鑰匙的極限

C試著寫入——然後看它被擋下來
請用同一把 token,試著在那個 Notion 資料庫裡新增一筆紀錄,
店名寫「測試店」。如果失敗了,把伺服器回你的錯誤訊息原文貼給我,
並用白話解釋這個錯誤是什麼意思。
🚫權限是真的存在的東西

剛剛那個失敗不是 bug,是設計。這把鑰匙被設定成只能讀——就算你(或 AI)想寫,Notion 那端也會拒絕。權限不是靠大家講好不要亂來,是對方真的做得到擋你。

所以第 04 節那面牆,其實有兩層:你的按鈕根本沒把資料送出去(那是頁面還沒接上寫入通道),而就算送出去,你手上這把鑰匙也寫不進去(那是權限)。等一下要拆的是第一層——第二層永遠不會交到你手上。

06前端 vs 後端=信任邊界

前端跑在使用者的手上——他的瀏覽器、他的裝置。這句話有多實在,看一次就懂:

  • 在你自己的頁面上右鍵 → 檢查,看那一整片程式碼——那是你寫的東西,攤在任何人面前
  • 想一下:如果剛剛那把鑰匙寫在裡面,現在誰看得到它?

後端則跑在你鎖起來的房間裡,使用者看不到裡面的判斷,也改不了它。

⚖️一條鐵律

任何「必須為真」的事情——價格對不對、這個人有沒有權限、密碼對不對——都要在後端判斷。因為前端的任何判斷,使用者自己就能繞過去。

前端:使用者的手上 程式碼全部看得到、改得到 任何判斷都可以被繞過 ✕ 不能放祕密、不能當真相 信任邊界 後端:你鎖起來的房間 使用者看不到裡面在做什麼 「必須為真」的事都在這裡判斷 ✓ 祕密、權限、共用真相
圖 6.1前端 vs 後端=信任邊界:前端跑在使用者手上、可被看光與竄改;「必須為真」的事一定在後端判斷。
回頭看第 01 節那張圖:中間那台伺服器就是這條界線。金鑰放在它裡面,是因為它在「鎖起來的房間」那一側。
07危險雷達三檢查表

你不需要會做後端,但你要有一個雷達,知道「這件事我不該碰,要交給工程師」。三個問題,中一個就要停手

🔑 ① 有祕密嗎? API key、密碼、外流會出事的東西 🪪 ② 需要信任判斷嗎? 他是誰?他有權限做這件事嗎? 🌐 ③ 是共用的真相嗎? 全公司必須看到同一份的資料 中一項 → 停手,交給工程師
圖 7.1危險雷達三檢查表:祕密?信任判斷?共用真相?——中一項就站在國界上了,喚工程師。
拿你自己的工作來試:
「活動報名頁顯示倒數天數」→ 三條都沒中,前端自己做沒問題。
把折扣碼寫在報名頁裡,輸入對了就打折」→ 中第一條(祕密):任何人右鍵檢查就看得到那組折扣碼。
用網址參數決定要不要給折扣」→ 中第二條(信任判斷):使用者自己改網址就能給自己打折。
「讓所有人看到同一份揪團名單」→ 中第三條(共用真相),這正是我們今天的處境。
08動手三:投票(寫入 + 讀回)

前面那張店家表你只能讀。現在做第二件事:投票——這次是真的寫進去,而且寫完馬上讀回來變成畫面上的票數。

🗳️兩張表,兩種事

店家表是「有哪些店」——講師維運,你只讀。投票紀錄是「誰想吃哪一家」——每個人一直加,只增不改。
畫面上那個「3 人想吃」的數字,不是誰手動填的,是系統去數投票紀錄算出來的

你的頁面依然拿不到可寫的鑰匙——它會去拜託那台伺服器代勞。伺服器手上有另一把可寫的鑰匙,藏在你看不到的房間裡。

你的前端 ✓ 完全沒有密碼 「幫我加一筆」 Proxy(代理人) 工程師預先建好 🔑 密碼藏在這裡 安全寫入 共用 Notion 資料庫 全公司同一份真相
圖 8.1Proxy=你和危險地帶之間的代理人:前端只說「幫我加一筆」,密碼安全地藏在代理人的後端。

起手式 · 用自己的話改

D把「我要跟」變成真的投票
我有一個「我要跟」按鈕,目前點下去只有我自己的畫面會變。
請改成:點擊時,除了原本的動作之外,再把這一票送到這個網址:

https://vibe-course.mixxin.tw/api/join

用 POST,送出的內容長這樣:{"name": "店名", "who": "我的名字"}
(我的名字請填「REPLACE_ME」)

送出成功之後,在那張卡片上顯示「已投票 ✓」。
送出失敗的話,要在同一張卡片上顯示一段紅色的提示,內容是伺服器回給我的錯誤訊息
(回應的 JSON 裡有一個 error 欄位),前面加上「這一票沒送出去:」。
失敗時人數不要加,也不要讓整個頁面壞掉。

另外,請把每張卡片上的人數改成顯示「目前幾個人想吃」,
數字來自剛剛抓回來的資料,不要自己在前端算。
  • 貼上 Prompt D,接上投票通道
  • 按下「我要跟」——然後打開 跟團紀錄表:你剛剛那一票出現在最下面了
  • 重整自己的頁面:卡片上的人數變成從投票紀錄算出來的
  • 等 30 秒,再請旁邊的同事重整他的頁面(伺服器有做快取,資料不是每秒都重抓)——這次他看得到你那一票
  • 用下面的 Prompt E 讓 AI 檢查一次,確認你的程式碼裡沒有金鑰
沒看到?照這個順序查:跟團紀錄表上有沒有你那筆?沒有=送出失敗,去 Console 看紅字。=送出成功,那就是快取還沒過期,再等一下重整。② 還是不行的話,把 Console 的紅字整段貼給 AI。

現在故意讓它失敗一次——這段很重要

你剛剛在 Claude Code 裡看過唯讀金鑰被擋(那個 403)。但那是看到的。使用者看到的是什麼?現在把它演出來。

  • 把你程式碼裡投票的那個網址,暫時改成 https://vibe-course.mixxin.tw/api/join-demo-fail(找不到的話跟 AI 說「把投票的網址改成這個」)
  • 重整頁面,按下「我要跟」
  • 卡片上出現紅字:「這一票沒送出去:伺服器沒有可用的金鑰,這一票寫不進去」,而且人數沒有增加
  • 打開 跟團紀錄表確認——真的沒有多出那一筆
  • 把網址改回 https://vibe-course.mixxin.tw/api/join,再投一次,確認又正常了
🔑這就是那把鑰匙的份量

剛剛那個端點是課程做的教具,它永遠會失敗,模擬的情況是:伺服器手上那把可寫的鑰匙過期了、被撤銷了、或根本沒設。

注意畫面上發生了什麼——你的程式碼一行都沒錯,按鈕、網址、送出的內容全都對。但鑰匙不能用,這一票就是進不去。整個功能的成敗,押在一個你看不到、也碰不到的東西上。

這也是為什麼那把鑰匙不能放在你的頁面裡:放在頁面裡,任何人打開原始碼就能拿走它,拿去亂寫;放在伺服器裡,它壞掉的時候只有管理的人能修——而使用者看到的,就是你剛剛做出來的那行紅字。

順手學到的第二件事:你剛剛做的「把伺服器回的錯誤原文顯示出來」,比寫死一句「失敗了」有用得多。誠實的失敗——告訴使用者到底怎麼了——是第 4 堂補課那頁講的同一件事。
E自我檢查沒有洩漏祕密
請檢查我目前的頁面程式碼,
確認裡面沒有任何金鑰、密碼、或看起來像祕密的字串。
如果有,告訴我在第幾行。
Wow 時刻 → 你剛剛做的事,是這門課從第一天就辦不到的:你的動作,出現在別人的螢幕上。而且你在三個地方同時看到它——你的畫面、同事的畫面、還有 Notion 那張表這就是「共用真相」。
兩人互看彼此手機、露出「成功了」表情的氛圍照
圖 8.2同事的裝置上看得到你剛剛加的紀錄——共用真相第一次成立。
還有一件事沒解決:現在那張表上,每一筆的「作者」都是那台伺服器,不是你本人。要怎麼讓它真的以你的名義寫進去?那需要你自己出面授權——那是之後的主戲
·講師課前準備事項本課程唯一硬依賴
  • 建立兩張 Notion 表:店家表(講師維運,只讀)+跟團紀錄表(學員寫入,只增不改)。詳見 repo 的 WORKER-SPEC.md
  • 確認全班都打得開那兩張 Notion 表(連結已印在教材的「本堂材料」區塊)
  • 建 integration A(只勾 Read content)=發給學員的唯讀金鑰;建 integration B(Read + Insert)=只放伺服器
  • 把該 database 分享給這兩個 integration(未分享會回 404,不是 403)
  • Worker:GET 端點(查 data source、整平成乾淨 JSON、加 30–60 秒快取)+ POST 端點(用 integration B 寫入)
  • 查詢一律用 /v1/data_sources/{id}/query 且 Notion-Version 帶 2025-09-03(舊的 databases 端點官方已標 removed)
  • CORS 必須開,而且要允許 null origin——學員多半是直接開本機檔案,瀏覽器送出的 Origin 是 null,只做來源白名單會讓全班同時失敗,而且錯誤只出現在 Console
  • POST 端點加速率限制,避免公開寫入被濫用(但不要用來源檢查當唯一防線,見上一條)
  • 寫入成功後主動清掉 GET 的快取,或把快取縮到 30 秒內——否則學員寫進去卻看不到,會誤判成失敗
  • 把兩個端點的完整網址與一個範例參數寫在投影片上(不要讓學員自己拼 API 文件)
  • 實測一次:拿學員那把唯讀金鑰試寫入,確認真的被擋(403)
·作業與檢核

作業:一張 issue + 一個 PR——把你的頁面接上店家表(讀)並讓「我要跟」能投票(寫)。PR 描述回答這三題:

  • 店家表跟投票紀錄是兩張不同的表,它們差在哪?為什麼一個只能讀、一個可以寫?
  • 為什麼我的頁面不能直接去問 Notion?中間那台伺服器在幫我做什麼?
  • 這個改動撞到危險雷達哪一條?為什麼一定要透過後端完成?

講師 / 助教檢核重點

  • 頁面顯示的總數與同學一致(確認是讀同一份)
  • 每人本機有 .env.local(顯示隱藏檔看得到),且 GitHub Desktop 的 Changes 清單上看不到它
  • 每人親眼看過「唯讀金鑰試寫入被擋」的錯誤訊息
  • 每人的「我要跟」成功寫進 Notion,且被同事的裝置看到
  • 每人頁面程式碼中確認沒有金鑰字串
  • 每人能覆誦危險雷達三檢查表