Hono 工程化实战 06:环境变量、配置和 Secret 管理
wxk1991 Lv5

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
公开配置
运行时配置
secret

公开配置:

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)
}

// signup
})

配置解析集中起来,后续调整会简单很多。


六、按环境管理 CORS

CORS 是最容易配置错的地方之一。

开发环境:

1
http://localhost:5173

测试环境:

1
https://staging.example.com

生产环境:

1
https://app.example.com

不要在生产使用:

1
origin: '*'

尤其是带 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

不一定所有项目一开始都要做这么完整。

但至少要知道:

1
secret 不是永远不会换

如果系统设计完全不支持轮换,等真正需要轮换时会很痛。


八、不要把 secret 写进日志

配置错误排查时,很多人会打印:

1
console.log(c.env)

不要这么做。

里面可能有:

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 让应用很轻,但配置管理不能轻率。