Hono 工程化实战 06:环境变量、配置和 Secret 管理
很多生产事故,不是代码逻辑写错。
而是配置错了。
比如:
1 2 3 4 5 6
| 生产环境连到了测试数据库 CORS origin 写成了 * JWT_SECRET 没配置 回调地址还是 localhost 调试日志在生产打开 对象存储 bucket 名写错
|
Hono 项目通常很轻,配置也容易被写得很随意。
但项目一旦进入生产,配置管理就必须严肃起来。
这篇文章讲 Hono 项目怎么管理环境变量、运行时配置和 secret。
技术栈快照时间:2026-06-23。Hono adapter helper、Cloudflare Workers bindings、Node.js 环境变量管理方式会更新,生产使用前以官方文档为准。
一、配置不是普通常量
代码常量可以写死:
1
| const DEFAULT_PAGE_SIZE = 20
|
但配置不应该随便写死:
1 2
| const API_BASE_URL = 'https://api.example.com' const JWT_SECRET = 'abc123'
|
配置通常和环境有关:
1 2 3 4
| development test staging production
|
不同环境可能有不同:
1 2 3 4 5 6 7
| 数据库 CORS origin 对象存储 bucket 第三方 API key 日志级别 功能开关 回调地址
|
把这些写进代码,会让部署和安全都变得危险。
二、配置分三类
我习惯把配置分成三类:
公开配置:
1 2 3
| APP_NAME PUBLIC_SITE_URL PUBLIC_API_BASE_URL
|
运行时配置:
1 2 3 4
| NODE_ENV LOG_LEVEL CORS_ORIGIN FEATURE_SIGNUP_ENABLED
|
secret:
1 2 3 4 5
| JWT_SECRET DATABASE_URL STRIPE_SECRET_KEY GITHUB_CLIENT_SECRET WEBHOOK_SECRET
|
规则很简单:
1 2 3
| 公开配置可以进前端 运行时配置只给后端 secret 绝不能进前端和 Git
|
不要因为都是 env,就混成一团。
三、启动时校验配置
配置错误应该在启动时失败。
不要等用户请求来了才发现 JWT_SECRET 是空的。
Node.js 项目可以用 Zod 校验:
1 2 3 4 5 6 7 8 9 10 11
| import { z } from 'zod'
const ConfigSchema = z.object({ NODE_ENV: z.enum(['development', 'test', 'staging', 'production']), PORT: z.coerce.number().default(3000), CORS_ORIGIN: z.string().url(), JWT_SECRET: z.string().min(32), DATABASE_URL: z.string().url(), })
export const config = ConfigSchema.parse(process.env)
|
这样如果配置缺失,应用启动就会报错。
这比线上某个接口突然 500 好太多。
四、Cloudflare Workers 的 Bindings
在 Cloudflare Workers 里,环境变量和资源通常通过 bindings 注入。
Hono 里可以这样声明:
1 2 3 4 5 6 7 8 9
| type Bindings = { CORS_ORIGIN: string JWT_SECRET: string DB: D1Database CACHE_KV: KVNamespace R2_BUCKET: R2Bucket }
const app = new Hono<{ Bindings: Bindings }>()
|
使用:
1 2 3 4 5 6 7 8
| app.get('/api/config-check', (c) => { return c.json({ data: { corsOriginConfigured: Boolean(c.env.CORS_ORIGIN), dbConfigured: Boolean(c.env.DB), }, }) })
|
资源绑定和字符串配置要分清楚:
1 2 3 4
| CORS_ORIGIN 是字符串 DB 是 D1 binding CACHE_KV 是 KV binding R2_BUCKET 是 R2 binding
|
类型写清楚,handler 里就不容易乱用。
五、不要到处直接读 env
错误做法:
1 2 3 4 5
| app.get('/api/posts', async (c) => { const origin = c.env.CORS_ORIGIN const jwtSecret = c.env.JWT_SECRET const feature = c.env.FEATURE_X })
|
不是说不能用 c.env。
而是不要让配置读取散落在所有业务代码里。
更推荐封装:
1 2 3 4 5 6 7
| function getRuntimeConfig(env: Env) { return { corsOrigin: env.CORS_ORIGIN, jwtSecret: env.JWT_SECRET, isSignupEnabled: env.FEATURE_SIGNUP_ENABLED === 'true', } }
|
handler:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| app.post('/api/signup', async (c) => { const config = getRuntimeConfig(c.env)
if (!config.isSignupEnabled) { return c.json({ error: { code: 'SIGNUP_DISABLED', message: '暂未开放注册', }, }, 403) }
})
|
配置解析集中起来,后续调整会简单很多。
六、按环境管理 CORS
CORS 是最容易配置错的地方之一。
开发环境:
测试环境:
1
| https://staging.example.com
|
生产环境:
不要在生产使用:
尤其是带 cookie 的接口。
可以这样写:
1 2 3 4 5 6 7 8 9 10 11 12
| import { cors } from 'hono/cors'
app.use('/api/*', async (c, next) => { const config = getRuntimeConfig(c.env)
return cors({ origin: config.corsOrigin, credentials: true, allowHeaders: ['Content-Type', 'Authorization'], allowMethods: ['GET', 'POST', 'PATCH', 'DELETE', 'OPTIONS'], })(c, next) })
|
如果有多个 origin,配置成逗号分隔,然后解析成白名单。
七、secret 轮换要提前设计
很多系统只有一个 JWT_SECRET。
用几年不变。
这很危险。
更好的做法是支持 key id:
1 2 3
| JWT_CURRENT_KID=2026-06 JWT_SECRET_2026_06=... JWT_SECRET_2026_03=...
|
签发 token 时写入 kid。
验证 token 时按 kid 找 secret。
这样可以做到:
1 2 3
| 新 token 用新 secret 旧 token 在过渡期仍可验证 过渡期结束后移除旧 secret
|
不一定所有项目一开始都要做这么完整。
但至少要知道:
如果系统设计完全不支持轮换,等真正需要轮换时会很痛。
八、不要把 secret 写进日志
配置错误排查时,很多人会打印:
不要这么做。
里面可能有:
1 2 3 4
| JWT_SECRET DATABASE_URL 第三方 API key Webhook secret
|
可以写一个安全检查:
1 2 3 4 5
| console.info('config_loaded', { corsOrigin: config.corsOrigin, logLevel: config.logLevel, hasJwtSecret: Boolean(config.jwtSecret), })
|
只记录“是否存在”,不要记录真实值。
九、本地开发配置
本地开发可以有 .env。
但要确保:
1 2 3 4
| .env 不进 Git .env.example 进 Git secret 用示例值 README 写清楚如何配置
|
.env.example:
1 2 3 4 5
| NODE_ENV=development PORT=3000 CORS_ORIGIN=http://localhost:5173 JWT_SECRET=replace-with-a-long-random-secret DATABASE_URL=postgres://user:password@localhost:5432/app
|
新人拉项目后,不应该靠问人才能跑起来。
配置样例是工程化的一部分。
十、功能开关也是配置
很多功能不适合直接上线打开。
比如:
1 2 3 4 5
| 新注册流程 新支付渠道 新缓存策略 新 AI 模型 实验性 WebSocket
|
可以先用简单环境变量:
1
| FEATURE_NEW_CHECKOUT=true
|
代码:
1 2 3 4 5
| if (config.features.newCheckout) { return newCheckout(c) }
return oldCheckout(c)
|
功能开关不要滥用。
长期不用的开关要清理。
否则代码会变成一堆历史分支。
十一、配置检查接口
可以给内部环境做一个配置检查接口。
注意不要返回 secret。
1 2 3 4 5 6 7 8 9 10 11 12
| app.get('/internal/config-check', requireAdmin(), (c) => { const config = getRuntimeConfig(c.env)
return c.json({ data: { nodeEnv: config.nodeEnv, corsOrigin: config.corsOrigin, features: config.features, hasJwtSecret: Boolean(config.jwtSecret), }, }) })
|
这个接口只给内部管理员或部署检查使用。
不要公开到公网。
它的价值是快速发现:
1 2 3 4
| 环境配错 功能开关错 secret 缺失 CORS 写错
|
十二、配置管理清单
Hono 项目上线前,至少检查:
1 2 3 4 5 6 7 8 9 10
| 1. 所有配置都有来源说明 2. secret 没有写进代码和 Git 3. 启动时校验必需配置 4. CORS 按环境配置 5. 本地有 .env.example 6. 日志不打印 secret 7. 配置读取有统一封装 8. 生产和测试资源不会混用 9. secret 有轮换思路 10. 功能开关有清理计划
|
配置不是小事。
它是代码运行时的另一半。
Hono 让应用很轻,但配置管理不能轻率。