Workers Bindings 與 Node.js 相容:用 env 宣告式連接資源、nodejs_compat 用 node: 模組 | Cloudflare 完整教學
前九篇,我們反覆用
env存取 KV、D1、Queue,把它當成理所當然——但這個把 Cloudflare 服務「注入」進 Worker 的機制,正是整個平台威力的核心。這一篇,我們正式拆解 Bindings:什麼是 Binding(在wrangler.jsonc宣告式連接資源,不用連線字串、不用金鑰)、env物件怎麼運作、KV / R2 / D1 / Durable Objects / Queues / AI / Service / Secrets 各種 binding 的全貌、如何用wrangler types自動生成Envinterface 型別,以及開啟nodejs_compatflag 後,哪些node:模組能用、哪些只是空殼。也徹底講清楚環境變數 vs Secrets vs Binding 的差別。這一篇同時為我們的 CF-1 運算核心收尾。
前言
上一篇《Cron Triggers 排程》,我們讓 Worker 學會「看時鐘做事」——用 cron 表達式描述時間、匯出 scheduled handler,做出不靠人觸發、時間到就自己跑的定時任務。但你有沒有注意到,無論是 fetch、scheduled 還是 WebSocket handler,它們的第二個參數永遠是同一個東西:env。我們一路用 env.SESSIONS、env.DB、env.TASK_QUEUE 存取各種 Cloudflare 服務,卻從沒認真問過:這個 env 到底是什麼?它裡面的 KV、D1、Queue 是怎麼跑進來的?
答案就是本篇的主角——Bindings(綁定)。先給一句話定義:Binding 是 Cloudflare Workers 用來「宣告式連接資源」的機制。你不在程式碼裡放連線字串或 API 金鑰,而是在 wrangler.jsonc 用一段設定「宣告」你要用哪個資源,平台在部署時就把它「綁」到你的 Worker,執行時透過 env 物件直接拿到一個已經接好、免認證的物件。
打個比方:傳統存取資料庫或外部服務,像是「自己開車出門辦事」——你得知道地址(host)、帶好鑰匙與證件(帳密、API Key)、自己開上路(建立連線)、還要在門口通過保全驗證(認證),一趟往返費時費力,鑰匙掉了還會出大事。而 Binding 像是「你家牆上有一排已經接好的水管與電線插座」——你不需要知道自來水廠在哪、也不需要出示證件,只要打開 env.MY_KV 這個「水龍頭」,水就來了。管線的鋪設、驗證、授權,平台在你「宣告」的那一刻就幫你接好了。你只描述「我要接哪個資源」,剩下的交給平台。
這種「你只宣告要什麼、不管怎麼連」的風格,就是所謂的宣告式(declarative)。它帶來三個實際好處:零憑證外洩風險(程式碼裡沒有任何密碼)、零連線樣板(不用寫建立連線與認證的程式)、極低延遲(資源就在 Cloudflare 網路內,無額外網路跳轉)。
讀完本篇,你會掌握:
- Binding 是什麼、
env如何運作——宣告式連接資源的設計哲學,以及env物件如何在 handler 裡注入所有資源 - 常見 binding 種類概覽——KV / R2 / D1 / Durable Objects / Queues / AI / Service Bindings / Secrets,各自解決什麼問題、
wrangler.jsonc設定鍵是什麼 Envinterface 與wrangler types——如何為env建立 TypeScript 型別,以及為何該用自動生成而非手寫nodejs_compat與node:模組——開啟相容性 flag 後,哪些 Node.js 模組完整支援、哪些只是 stub 存根,以及環境變數 vs Secrets vs Binding 的界線
核心概念
Binding 的運作原理:宣告 → 注入
先把整條路徑走一遍,你就懂 Binding 的全貌了。它分成三步:
- 宣告(在
wrangler.jsonc):你用一段設定告訴 Cloudflare——「這個 Worker 要用一個 KV namespace,我在程式裡叫它MY_KV,它的實際 id 是xxx」。這裡的關鍵是binding這個欄位——它就是你之後在程式碼中透過env存取時用的變數名。 - 綁定(部署時):執行
wrangler deploy時,Cloudflare 平台讀取這段宣告,把對應的實體資源「掛」到你的 Worker 上,並在你的 Worker 與資源之間建立好一條免認證、走內部網路的通道。 - 注入(執行時):每次 handler 被呼叫,平台就把所有 binding 打包成一個
env物件,當作參數傳進來。於是env.MY_KV就是那個接好的 KV 物件,env.DB就是那個 D1 資料庫,直接呼叫方法即可。
一張圖看懂這個「宣告式連接」:
wrangler.jsonc(宣告) Worker 執行時(注入)
┌────────────────────────┐ ┌──────────────────────────┐
│ kv_namespaces: │ │ fetch(request, env, ctx){│
│ binding: "MY_KV" ────┼──────┼──► env.MY_KV.get(...) │
│ id: "abc123" │ 綁定 │ │
│ d1_databases: │ │ env.DB.prepare(...) │
│ binding: "DB" ───────┼──────┼──► │
└────────────────────────┘ └──────────────────────────┘
「我要什麼資源」 「直接用,免連線、免金鑰」
對比一下傳統做法,差異就很鮮明:
| 面向 | 傳統(連線字串 / API Key) | Binding(宣告式) |
|---|---|---|
| 憑證 | 程式碼或環境變數裡有帳密/金鑰 | 無需憑證,不在程式碼裡放任何密碼 |
| 連線 | 自己建立連線、通過認證 | 平台接好,直接拿到可用物件 |
| 延遲 | 多一趟網路往返 + 認證 | 零額外跳轉,資源就在 CF 網路內 |
| 設定位置 | 散落在程式碼各處 | 集中在 wrangler.jsonc |
常見 binding 種類概覽
env 上能掛的 binding 種類很多,以下是最常見、你一定會遇到的幾種。本篇只做全景概覽——每一種後續都有專章詳談,這裡先建立地圖:
| Binding | 解決什麼問題 | env 用法(示意) | wrangler.jsonc 設定鍵 |
|---|---|---|---|
| KV | 全球讀取快、最終一致的鍵值儲存 | env.MY_KV.get / put / delete / list | kv_namespaces |
| R2 | S3 相容的物件儲存(圖片、檔案),無出口流量費 | env.MY_BUCKET.get / put / delete | r2_buckets |
| D1 | SQLite 關聯式資料庫,支援 SQL | env.DB.prepare(...).bind(...).first / run | d1_databases |
| Durable Objects | 有狀態的協調單元(聊天室、計數器) | env.DO.idFromName(...).get(id) | durable_objects |
| Queues | 非同步訊息佇列,可批次、可重試 | env.MY_QUEUE.send(...) / queue handler | queues |
| Workers AI | 在邊緣執行 LLM 等 AI 推論 | env.AI.run(model, inputs) | ai |
| Service Bindings | Worker 直接呼叫另一個 Worker(RPC / fetch) | env.SERVICE.fetch(...) / 直接方法呼叫 | services |
| Secrets | 加密儲存的機密字串(金鑰、密碼) | env.API_KEY(字串) | wrangler secret put |
有幾個觀念要先建立:
- KV / R2 / D1 / DO / Queues / AI / Service 這些 binding,你拿到的是一個可以呼叫方法的物件(有
get、put、send、run、fetch等方法),它們代表「一個 Cloudflare 服務資源」。 - Secrets 與環境變數則不同,你拿到的是一個字串值(例如
env.API_KEY就是那串金鑰本身),它們代表「一個設定值」。 - 兩者都掛在同一個
env上、都靠宣告注入,但一個是「服務」、一個是「值」——這個分野後面會反覆用到。
Env interface:給 env 型別
env 很好用,但它有個問題:如果你打錯字寫成 env.MY_KY(把 KV 打成 KY),或忘了某個 binding 的方法名,JavaScript 不會事先告訴你,得等執行時才炸。在 TypeScript 裡,我們用一個 Env interface 來描述 env 上有哪些 binding、各是什麼型別,編輯器就能自動補全、事先抓錯:
// 描述這個 Worker 的 env 上有哪些 binding
interface Env {
MY_KV: KVNamespace; // KV binding
DB: D1Database; // D1 binding
MY_BUCKET: R2Bucket; // R2 binding
MY_QUEUE: Queue; // Queues producer binding
AI: Ai; // Workers AI binding
API_BASE_URL: string; // 環境變數(明文)
API_KEY: string; // Secret(加密)
}
注意:binding(如 MY_KV)的型別是專門的類別(KVNamespace、D1Database 等,來自 Cloudflare 的型別定義),而環境變數與 Secret 的型別就是 string——因為它們本質上就是字串。這再次呼應了上一節「服務物件 vs 設定值」的分野。
手寫 Env 很容易漏、也容易和 wrangler.jsonc 不同步。所以更推薦用指令自動生成:
# 讀取 wrangler.jsonc,自動生成與所有 binding 對應的型別定義
npx wrangler types
它會產生一份型別檔(通常是 worker-configuration.d.ts),裡面就是根據你 wrangler.jsonc 裡所有 binding 生成的 Env 定義。每次改了 binding,重跑一次即可,永遠與設定同步。
實作範例
概念齊了,我們把 binding 宣告、env 存取、型別、以及 nodejs_compat 用 node: 模組,串成幾個完整可執行的範例。
範例一:宣告多種 binding 並在 env 存取
先在 wrangler.jsonc 一次宣告 KV、D1、R2、以及環境變數:
// wrangler.jsonc
{
"name": "my-worker",
"main": "src/index.ts",
"compatibility_date": "2026-08-01",
// KV binding:程式裡叫 MY_KV
"kv_namespaces": [
{ "binding": "MY_KV", "id": "<你的 KV namespace id>" }
],
// D1 binding:程式裡叫 DB
"d1_databases": [
{ "binding": "DB", "database_name": "my-app-db", "database_id": "<你的 D1 id>" }
],
// R2 binding:程式裡叫 MY_BUCKET
"r2_buckets": [
{ "binding": "MY_BUCKET", "bucket_name": "my-app-storage" }
],
// 環境變數(明文,非機密):程式裡叫 env.API_BASE_URL
"vars": {
"API_BASE_URL": "https://api.example.com",
"ENVIRONMENT": "production"
}
}
再看 Worker 本體如何透過 env 使用它們——注意程式裡完全沒有任何連線字串、host、帳密:
// src/index.ts —— 透過 env 使用多種 binding
interface Env {
MY_KV: KVNamespace;
DB: D1Database;
MY_BUCKET: R2Bucket;
API_BASE_URL: string;
ENVIRONMENT: string;
}
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
// 1) 環境變數:直接當字串用
console.log(`目前環境:${env.ENVIRONMENT}, API 位址:${env.API_BASE_URL}`);
// 2) KV binding:免連線、免金鑰,直接呼叫方法
await env.MY_KV.put("greeting", "Hello from KV");
const greeting = await env.MY_KV.get("greeting");
// 3) D1 binding:用 prepared statement 防注入
const user = await env.DB
.prepare("SELECT name FROM users WHERE id = ?")
.bind(1)
.first<{ name: string }>();
// 4) R2 binding:讀一個物件
const object = await env.MY_BUCKET.get("logo.png");
const hasLogo = object !== null;
return Response.json({
environment: env.ENVIRONMENT,
greeting,
user,
hasLogo,
});
},
} satisfies ExportedHandler<Env>;
看見了嗎?env.MY_KV、env.DB、env.MY_BUCKET 全都「直接就能用」——沒有 new Client()、沒有 connect()、沒有 API Key。這就是宣告式 binding 的威力:所有連線與授權,平台在你宣告的那一刻就接好了。
範例二:用 Secrets 存放機密(而非明文)
上一個範例的 API_BASE_URL 是非機密設定,用明文 vars 沒問題。但如果你有一把機密金鑰(例如呼叫第三方 API 的 token),絕對不能寫進 wrangler.jsonc 的 vars,也不能硬寫在程式碼——因為那會進版控、會外洩。正確做法是用 Secret:
# 設定一個加密的 Secret(互動式,會提示你輸入值)
npx wrangler secret put THIRD_PARTY_API_KEY
# CI/CD 環境可從 stdin 餵入
echo "sk-xxxxxxxx" | npx wrangler secret put THIRD_PARTY_API_KEY
# 查看已設定的 Secret 名稱(值看不到,已加密)
npx wrangler secret list
設定後,Secret 的用法和環境變數一模一樣——都掛在 env 上、都是字串:
// src/index.ts —— 用 Secret 呼叫第三方 API
interface Env {
API_BASE_URL: string; // 明文環境變數
THIRD_PARTY_API_KEY: string; // 加密 Secret
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
// env.THIRD_PARTY_API_KEY 由平台安全注入,程式碼裡看不到明文
const res = await fetch(`${env.API_BASE_URL}/data`, {
headers: { "Authorization": `Bearer ${env.THIRD_PARTY_API_KEY}` },
});
return Response.json(await res.json());
},
} satisfies ExportedHandler<Env>;
差別只在「哪裡設定」:明文設定進 vars、機密進 wrangler secret put。取用方式對程式碼而言毫無二致。
範例三:開啟 nodejs_compat,用 node: 模組
有時你想用 Node.js 生態裡熟悉的 API,例如 node:crypto 的 createHmac,或 node:path 的路徑操作。這需要在 wrangler.jsonc 開啟 nodejs_compat 相容性 flag:
// wrangler.jsonc
{
"name": "my-worker",
"main": "src/index.ts",
// nodejs_compat 需要 compatibility_date 為 2024-09-23 或更新
"compatibility_date": "2026-08-01",
"compatibility_flags": ["nodejs_compat"]
}
開啟後,就能 import 那些完整支援的 node: 模組:
// src/index.ts —— 用 node:crypto 做 HMAC 簽章、node:path 處理路徑
import { createHmac } from "node:crypto";
import path from "node:path";
interface Env {
HMAC_SECRET: string; // 建議用 wrangler secret put 設定
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
// 用 node:crypto 的 createHmac 計算簽章(Node.js 風格 API)
const body = await request.text();
const signature = createHmac("sha256", env.HMAC_SECRET)
.update(body)
.digest("hex");
// 用 node:path 組路徑
const assetPath = path.join("/assets", "images", "logo.png");
// assetPath === "/assets/images/logo.png"
return Response.json({ signature, assetPath });
},
} satisfies ExportedHandler<Env>;
要特別提醒:並不是所有 node: 模組都能真的用。支援程度分三級:
| 支援程度 | 代表模組 | 能不能用 |
|---|---|---|
| 完整支援 | node:buffer、node:crypto、node:events、node:path、node:stream、node:url、node:util、node:zlib、node:http/https | 可正常使用 |
| 部分支援 | node:os、node:dns、node:perf_hooks、node:console | 只有部分功能可用 |
| stub 存根 | node:child_process、node:worker_threads、node:vm、node:http2 | 可 import 但呼叫會報錯 |
那些 stub 存根之所以存在,是為了讓某些只做「這個模組存在嗎」檢查的第三方套件能順利載入——但你不能真的呼叫它們的功能(會拋 not implemented)。原因很單純:Workers 是邊緣的無伺服器環境,天生就沒有「開子行程」「讀寫本機磁碟」「多執行緒」這些概念。
補充:如果你只需要
AsyncLocalStorage(不需要整套 Node.js 相容),可以改用更輕量的nodejs_alsflag,啟動更快。
常見錯誤與最佳實踐
坑一:把 Secret、API 金鑰硬寫在程式碼或 wrangler.jsonc 的 vars 裡。
這是最危險、也最常見的錯誤。有人為了圖方便,直接把 const API_KEY = "sk-real-secret" 寫死在原始碼,或塞進 wrangler.jsonc 的 vars。問題是:這兩個地方都會進版控——一旦推上 GitHub,金鑰就等於公開,爬蟲幾分鐘內就會撈走盜用。正確做法:機密一律用 npx wrangler secret put,值會加密儲存、不進版控、設定後也讀不回明文;程式碼裡只透過 env.API_KEY 取用。明文的 vars 只放非機密設定(base URL、環境名、功能開關)。一句話:原始碼與 vars 裡永遠不該出現任何密碼。
坑二:想用 node: 模組,卻忘了開 nodejs_compat(或 compatibility_date 太舊)。
你 import { createHmac } from "node:crypto",結果部署或啟動時報錯找不到模組——多半是忘了在 wrangler.jsonc 加 "compatibility_flags": ["nodejs_compat"]。另一個常見變體是:flag 加了,但 compatibility_date 早於 2024-09-23,nodejs_compat 也不會生效。正確做法:確認 compatibility_flags 含 nodejs_compat,且 compatibility_date 為 2024-09-23 或更新。
坑三:以為 nodejs_compat = 完整 Node.js,想用 child_process、fs 讀寫檔案。
有人開了 nodejs_compat 就以為 Workers 變成完整 Node.js,興沖沖 import { exec } from "node:child_process" 想開子行程、或用 node:fs 讀寫本機檔案——import 沒報錯(因為是 stub 存根),但一呼叫就拋 not implemented,一頭霧水。正確認知:Workers 是邊緣無伺服器環境,沒有本機檔案系統、不能開子行程、沒有多執行緒。能用的是「純運算 + 標準加密/編碼/串流」這一類(crypto、buffer、path、stream、zlib…),而不是「開子行程、讀寫磁碟」那一類。要存檔案,用 R2 binding;要跑背景重活,用 Queues 或 Workflows——這才是 Workers 的正確姿勢。
坑四:手寫 Env interface,結果和 wrangler.jsonc 不同步。
手寫型別很容易漏一個 binding、或改了設定忘了改型別,導致 env.NEW_KV 明明宣告了卻沒型別、編輯器不補全,甚至誤把型別標錯。正確做法:用 npx wrangler types 自動生成型別——它直接讀 wrangler.jsonc,產出永遠與設定同步的 Env。每次改 binding 就重跑一次,把它加進你的 dev/build 前置步驟,一勞永逸。
坑五:分不清 binding 是「服務物件」還是「設定值」,用錯方法。
有人對 env.API_KEY(Secret,是字串)呼叫 .get(),或把 env.MY_KV(KV,是物件)當字串直接拼接——都會出錯。記住那條分野:KV / R2 / D1 / DO / Queues / AI / Service 這些 binding 給你的是可呼叫方法的物件;環境變數與 Secrets 給你的是字串。用之前先確認手上是「服務」還是「值」。
最佳實踐小結: 記三條——機密一律 wrangler secret put、絕不進版控;要用 node: 模組先開 nodejs_compat + 確認 compatibility_date,且只用得起純運算類 API;型別一律 wrangler types 自動生成、別手寫。守住這三條,你的 binding 就能用得又安全、又順手、又不出錯。
小結
上一篇《Cron Triggers 排程》,我們讓 Worker 學會「看時鐘做事」——用 cron 表達式與 scheduled handler 做出定時任務。這一篇,我們回頭把一路用到的 env 徹底拆開,深入了 Bindings 與 Node.js 相容:
- Binding 的運作原理——三步走:在
wrangler.jsonc「宣告」→ 部署時「綁定」→ 執行時透過env「注入」。宣告式連接資源,不用連線字串、不用金鑰、零額外網路跳轉。 - 常見 binding 種類——KV(鍵值)、R2(物件儲存)、D1(SQL)、Durable Objects(有狀態協調)、Queues(訊息佇列)、Workers AI(推論)、Service Bindings(Worker 互呼)、Secrets(機密字串),各有專屬
wrangler.jsonc設定鍵,後續都有專章詳談。 Envinterface 與wrangler types——為env建立型別讓編輯器補全、事先抓錯;binding 是服務類別、環境變數與 Secret 是string;一律用npx wrangler types自動生成、別手寫。nodejs_compat與node:模組——開 flag(需compatibility_date≥ 2024-09-23)後,node:crypto/path/buffer等完整支援、os/dns部分支援、child_process/worker_threads只是 stub 存根;Workers 是邊緣環境,沒有檔案系統與子行程。- 環境變數 vs Secrets vs Binding——非機密設定用
vars(明文)、機密字串用wrangler secret put(加密)、Cloudflare 服務資源用 Binding(物件)。三者都掛env,但安全性與用途天差地別。
到這裡,CF-1 運算核心正式收尾了——從 Workers 的執行模型、Request/Response、fetch、Cache、Streams、WebSockets、Cron,一路到今天的 Bindings 與 Node.js 相容,你已經完整掌握「Worker 這顆運算核心怎麼運作、怎麼連接周邊資源」。但寫好的 Worker 要怎麼真正跑起來、部署上線?這就要靠 Cloudflare 的官方命令列工具——Wrangler。下一篇《Wrangler 入門》,我們正式進入部署工具的世界,帶你從 wrangler init 建專案、wrangler dev 本地開發、到 wrangler deploy 一鍵上線,把前面學的一切變成活生生跑在邊緣的服務。
想先一睹官方 Bindings 與 Node.js 相容的完整清單,可以隨時參考 Cloudflare 官方 Bindings 文件。運算核心已就緒,我們下一篇《Wrangler 入門》見。