規則:可以查前六課、官方文件,也可以使用 Codex、Claude Code、AGY 等 coding agent;但你必須能說出資料怎麼流、身份怎麼被辨認、權限在哪裡被檢查、schema 怎麼部署,以及 production 壞掉時先查哪一層。

你的任務:Private Taskboard

做一個私人 task 管理 Web App。每個使用者都可以建立帳號、登入、建立自己的 tasks、修改完成狀態、刪除,並且永遠不能看到或操作其他使用者的資料。

最終 production data flow:

Browser
↓ same-origin request
Worker API
↓ validate / authenticate / authorize
D1
↓ rows
Worker JSON response
↓
Frontend state / DOM

產品本身可以很簡單。這一關評的是系統邊界,不是 UI 特效。

先建立新專案,不要直接改 lesson playground

private-taskboard/
├─ public/
│  ├─ index.html
│  ├─ styles.css
│  └─ script.js
├─ src/
│  └─ index.js
├─ migrations/
├─ wrangler.jsonc
├─ package.json
├─ package-lock.json
├─ .gitignore
├─ .dev.vars.example
└─ README.md

這個 repository 最後要能讓另一個開發者只看 README、環境欄位與 migrations,就知道怎麼把本機版本跑起來。

Checkpoint A|先畫清楚系統,不要先狂寫 code

在 README 放一個最小架構說明:

Static frontend → Worker routes → D1
Browser cookie → session lookup → current user
current user → owner-scoped task query

至少列出你準備做的 routes:

POST   /api/register
POST   /api/login
POST   /api/logout
GET    /api/me
GET    /api/tasks
POST   /api/tasks
PATCH  /api/tasks/:id
DELETE /api/tasks/:id

如果你不知道某個 route 的 input、output 與權限需求,先不要急著寫。

Checkpoint B|建立 versioned schema

用 migration 建立至少三張 tables:

users:id、email、password hash / salt、created_at。

sessions:token hash、user_id、expires_at、created_at。

tasks:id、user_id、title、done、created_at。

至少要有:

users.email UNIQUE
tasks.user_id → owner identity
NOT NULL constraints
primary keys

不要在 production Worker 每次收到 request 時才臨時 CREATE TABLE。

Checkpoint C|Register:原始密碼不能落地

POST /api/register 至少必須:

驗證 body 是 object。

normalize email。

驗證 email / password 基本格式。

使用 password hashing / KDF,不存 plain password。

拒絕重複 email。

response 不包含 hash、salt 或其他內部欄位。

至少測三種情況:正常註冊、重複 email、錯誤輸入。

Checkpoint D|Login:建立可撤銷 session

POST /api/login 必須完成:

email + password
↓ 查 user
↓ verify password
↓ random session token
↓ database 存 token hash
↓ Set-Cookie

Cookie 至少要考慮:

HttpOnly
Secure          # production HTTPS
SameSite=Lax
Path=/
Max-Age=...

錯誤 email 與錯誤 password 不要回兩種過度詳細的外部錯誤。

Checkpoint E|GET /api/me:身份必須來自 session

登入後:

GET /api/me

應該由 cookie → session → user 得到目前登入者,而不是讓 frontend 自己傳:

?user_id=3

未登入必須得到合理的 401。

Checkpoint F|Tasks 必須真正是 private data

GET /api/tasks 的 database query 必須帶 current user:

WHERE user_id = ?

建立 task 時,owner 也必須由 session 決定:

authenticated user.id → INSERT tasks.user_id

就算 client body 偷塞:

{
  "title": "steal ownership",
  "user_id": 999
}

backend 也不能採用 client 提供的 owner。

Checkpoint G|完成 owner-scoped CRUD

你的 task resource 至少支援:

Create → POST /api/tasks
Read → GET /api/tasks
Update → PATCH /api/tasks/:id
Delete → DELETE /api/tasks/:id

PATCH / DELETE 不能只有:

WHERE id = ?

至少要把 owner boundary 帶進 operation:

WHERE id = ? AND user_id = ?

如果 user B 猜到 user A 的 task id,也不能因此操作成功。

Checkpoint H|Frontend 不只是 API 測試器

做一個實際可以使用的 UI,至少包含:

Register form。

Login form。

目前使用者資訊。

Logout。

新增 task。

task list。

切換 done。

刪除 task。

Loading / empty / error states。

不要用 frontend localStorage 假裝資料持久化;資料真相必須在 production database。

Checkpoint I|Frontend state 和 server state 要分清楚

Frontend 可以保存:

currentUser
tasks
isLoading
errorMessage

但真正的身份與權限仍由 backend 決定。

例如 UI 可以根據 currentUser 顯示不同畫面,但任何 protected API 仍要重新從 session 驗證。

Checkpoint J|Validation 要在邊界發生

Task title 至少限制:

必須是 string。

trim 後不能空白。

設定合理最大長度,例如 200。

API 不應該接受:

{ "title": null }
{ "title": 123 }
{ "title": "" }
{ "title": "...超長..." }

Frontend validation 可以改善 UX,但 backend validation 才是 trust boundary。

Checkpoint K|錯誤格式必須可預期

至少統一成類似:

{
  "error": {
    "code": "INVALID_INPUT",
    "message": "title is required"
  }
}

500 response 不可以把 production stack、SQL、secret、filesystem path 原封不動回給 browser。

Server log 可以有更詳細資訊,但不得記錄完整 password、session token 或 API key。

Checkpoint L|Secret 與 config 不進 source

Repository 必須有:

.dev.vars.example

但不能 commit:

.dev.vars
.env
真實 API key
真實 session token

在 commit 前至少檢查一次:

git status
git diff

如果 secret 曾經進 Git history,要 rotate,不是只刪當前檔案。

Checkpoint M|Local D1 和 production D1 分開

先用 local development 驗證:

migration → local DB → Worker → frontend

再把 migration 套到 remote production database。

你必須知道「deploy code」和「apply schema migration」是兩個不同動作。

Checkpoint N|Production deploy

部署後應該形成:

GitHub main
↓ build / deploy
Cloudflare Worker + Static Assets
↓
D1 + platform secrets
↓
HTTPS production URL

Frontend API call 使用 relative URL:

fetch("/api/tasks", ...)

避免把 production hostname 硬寫進每個 frontend request。

Checkpoint O|真正做兩個帳號的權限測試

這不是可選項。

User A:註冊、登入、建立 task A1 / A2。

User A:確認重新整理後資料仍在。

User A:登出。

User B:建立另一個帳號並登入。

User B:看不到 A1 / A2。

User B:就算知道 A1 的 id,也不能 PATCH / DELETE。

User B:只能看到自己的 tasks。

如果只測一個帳號,你根本還沒驗證 authorization。

Checkpoint P|Logout 要真的讓 session 失效

Logout 後:

delete server-side session
+ expire browser cookie
→ protected request 回 401

不能只是 frontend 把 task list 隱藏起來。

Checkpoint Q|做 production smoke test

部署完成後從頭走一次:

□ 首頁載入正常。

□ Register 正常。

□ Login 正常,cookie 有 Secure / HttpOnly。

□ /api/me 正確。

□ Create / Read / Update / Delete 都正常。

□ Refresh 後資料仍在。

□ Logout 後 protected request 失敗。

□ 第二個帳號無法讀寫第一個帳號資料。

□ Network 沒有意外 500。

□ Production log 沒有 secret。

Checkpoint R|README 要讓作品可被理解

README 至少說清楚:

這個產品解決什麼問題。

Frontend / backend / database 架構。

Local development 怎麼啟動。

需要哪些 environment variables / bindings。

Migration 怎麼套用。

Production deployment 流程。

目前已知限制。

README 不是裝飾,它是讓專案可以被另一個人接手的最低文件。

Git history 也要像真的專案

至少拆出幾個可理解的 commits,例如:

project scaffold
database schema + migrations
register / login / sessions
owner-scoped task API
frontend task UI
validation + error handling
production deployment config

不要最後只留下:

final

這一條 commit。

可以用 AI coding agent,但驗收標準更高

Agent 可以幫你建立 Worker routes、D1 queries、frontend UI 或 deployment config,但你必須能回答:

這個 request 的 current user 從哪裡來?

原始 session token 存在哪裡?Database 存的是什麼?

為什麼 user B 無法更新 user A 的 task?

Migration 和 deploy 的先後關係是什麼?

哪一些值可以 commit,哪一些不能?

如果 production API 回 500,你先看哪裡?

如果只能說「agent 幫我寫的」,這一關還沒有通過。

不要把 Final 做成大型 SaaS

這一關不要求:

OAuth / Google Login。

Email verification。

Password reset。

Teams / organizations。

Payments。

WebSocket。

React / Next.js。

那些都可以以後加。現在最重要的是先把一個小系統做完整,而不是把十個功能都做到一半。

最終驗收清單

□ Frontend 與 API 可以完整互動。

□ Production 使用持久 database,不依賴 server RAM。

□ Schema 有 versioned migration。

□ Password 不以明文保存。

□ Session token 隨機產生,server 能 revoke。

□ Cookie 有合理的 HttpOnly / Secure / SameSite 屬性。

□ Protected route 未登入會被拒絕。

□ Tasks 由 current user 擁有。

□ Query / PATCH / DELETE 都守 owner boundary。

□ Client 不能自行指定 owner / admin 權限。

□ Request input 有 backend validation。

□ SQL / database access 使用 bound parameters。

□ Public 500 不洩漏內部錯誤。

□ Secret 不在 Git / frontend / logs。

□ Production 已完成兩帳號 isolation test。

□ GitHub 有可理解的 commit history。

□ README 能讓別人理解與啟動專案。

如果全部做到,你現在已經跨過哪條線?

Web Basics 結束時,你會做「會互動、會讀 API 的網站」。

Web App 結束時,你已經能做:

UI
↕
HTTP API
↕
Validation / Auth / Permissions
↕
Persistent Database
↕
Production Environment

這代表你不再只是把畫面做出來,而是可以把一個小型產品從 browser 一路做到 production data layer。

下一條路徑是 AI Builder:不是重新學一套網站,而是在你現在已有的 Web App 邊界上加入模型 API、Prompt、Context、Structured Output、RAG 與 Tool Calling。

Web App 完成條件:你能從零做出並部署一個有 frontend、API、持久 database、register / login / logout、server-side session、owner-based authorization、validation、safe errors、secrets 與 migrations 的小型 Web App,而且能自己說明它為什麼能工作。