4.2 第一支 Worker:路由、FetchHandler 與環境變數#
本節目標#
- 寫出第一支能運作的 Worker,理解
fetchhandler 的標準簽名 - 學會 Dashboard 與 wrangler 兩種註冊 Worker 的方法,以及它們各自適合的場景
- 正確區分「環境變數(純文字設定)」與「機密(加密的 secrets)」
- 從手寫
if/switch路由進化到使用 Hono 這類微型框架
FetchHandler:簽名就是合約#
Workers 的事件模型只有一個主要入口——fetch 事件。TypeScript 的型別定義揭示了它的合約:
export interface FetchHandler {
(
request: Request, // 標準 Fetch API 的 Request 物件
env: Env, // 綁定:變數、secrets、KV、D1…
ctx: ExecutionContext, // waitUntil / passThroughOnException
): Response | Promise<Response>;
}
三個參數:request(讀 method、URL、body)、env(wrangler 注入的綁定)、ctx(waitUntil 讓背景工作在回應後繼續)。最小的 Worker:
// src/index.ts
export interface Env {
NAME: string; // 環境變數(純文字)
API_KEY: string; // secret(加密儲存)
}
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === "/hello") {
return new Response(`Hello, ${env.NAME}!`);
}
return new Response("Not Found", { status: 404 });
},
};
關鍵觀念:env 不存在於 process.env。 Workers 沒有 Node 的 process 物件,一切綁定由 env 參數顯式注入,這也讓依賴在介面層面完全可見、可測試。
環境變數 vs Secrets#
兩者的讀取方式相同(都是 env.XXX),差異在儲存與可見性:
# wrangler.toml
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2025-01-01"
[vars]
NAME = "World"
FEATURE_FLAG = "on"
# secrets 不寫在 toml,用指令設定:
# npx wrangler secret put API_KEY
$ npx wrangler secret put API_KEY # 互動輸入值,加密儲存
規則很簡單:會進版本控制的都必須不是機密。vars 進 wrangler.toml,secrets 走 wrangler secret put。多環境可用 [env.staging.vars] 區塊覆寫。
註冊 Worker 的兩種方法#
方法一:Dashboard(所見即所得)#
到 dash.cloudflare.com → Workers & Pages → Create application,在瀏覽器編輯器貼上程式碼、按 Deploy 即可。適合原型驗證與示範,缺點是無版本控制、難以協作。
方法二:wrangler(工程化)#
$ npm create cloudflare@latest my-worker
$ cd my-worker
$ npx wrangler login
$ npx wrangler deploy # 佈署到 workers.dev 的子網域
工程化路徑的價值:程式碼進 git、設定進 toml、deploy 是不可變版本的發佈動作。
路由:從 if/switch 到 Hono#
簡單場景可直接操作 URL:
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const { pathname } = new URL(request.url);
const method = request.method;
if (method === "GET" && pathname === "/todos") return list(env);
if (method === "POST" && pathname === "/todos") return create(env, request);
const m = pathname.match(/^\/todos\/(\d+)$/);
if (method === "GET" && m) return get(env, Number(m[1]));
return new Response("Not Found", { status: 404 });
},
};
當路由數成長,if 樹每次請求線性掃描 \(O(n)\) ,且耦合 method、路徑、參數三個維度。
Hono:微型框架的解藥#
Hono 是專為 Workers 等邊緣環境設計的框架,零依賴、數 KB:
import { Hono } from "hono";
type Bindings = { NAME: string; API_KEY: string };
const app = new Hono<{ Bindings: Bindings }>();
app.get("/todos", (c) => c.json(listTodos(c.env)));
app.post("/todos", async (c) => c.json(await create(c.env, await c.req.json()), 201));
app.get("/todos/:id", (c) => c.json(getTodo(c.env, c.req.param("id"))));
app.notFound((c) => c.text("Not Found", 404));
export default app; // Hono 的 app 本身就是一個合法的 fetch handler
Hono 的泛型參數 Bindings 讓 c.env 擁有完整型別提示——與第 3 章 Axum 的提取器思想同構:把「從請求到資料」的解析規則寫進簽名。內部用 Trie 比對路由,複雜度與路徑長度相關、與路由數 \(n\) 無關。
小結與練習#
fetch(request, env, ctx)三參數簽名就是 Workers 的 ABI;env取代process.env。vars走wrangler.toml,secrets 走wrangler secret put,介面上都是字串。- Dashboard 適合原型,wrangler 適合工程;手寫路由適合兩三條,Hono 適合中型以上。
練習:
- 寫一個
/greet/:name路由,同時提供手寫版與 Hono 版,比對程式碼行數。 - 用
[env.preview.vars]為 staging 設定不同NAME,用wrangler deploy --env preview部署,驗證兩個子網域的輸出不同。 - 把
API_KEY改為 secrets 部署,證明wrangler.toml裡沒有這個值,但echo $API_KEY之外的讀取(API 呼叫本身)仍可運作。