跳轉到

4.2 第一支 Worker:路由、FetchHandler 與環境變數#

本節目標#

  • 寫出第一支能運作的 Worker,理解 fetch handler 的標準簽名
  • 學會 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 適合中型以上。

練習:

  1. 寫一個 /greet/:name 路由,同時提供手寫版與 Hono 版,比對程式碼行數。
  2. 用 [env.preview.vars] 為 staging 設定不同 NAME,用 wrangler deploy --env preview 部署,驗證兩個子網域的輸出不同。
  3. 把 API_KEY 改為 secrets 部署,證明 wrangler.toml 裡沒有這個值,但 echo $API_KEY 之外的讀取(API 呼叫本身)仍可運作。