使用Zod和TypeScript对Node.js环境变量进行验证。
停止在Node.js代码中到处读取process.env。通过Zod验证的env.ts文件可在启动时检测到缺失的密钥,并为应用提供类型化的TypeScript配置。
想象一下周五下午的部署场景:构建成功,服务器启动,大家都回家了。一小时后,用户反馈登录失败。原来是在生产环境中没人设置 JWT_SECRET。应用起初运行正常,直到第一个请求触碰到需要该缺失值的代码时才出问题。
在整个代码库中到处读取 process.env 会增加这类故障的发生概率。下文将展示如何用 Zod 和 TypeScript 替代这种做法,采用经过验证的类型化配置模块。
常规方法
大多数 Node.js 代码会在需要配置的地方直接获取它:一个文件中存放端口信息,另一个文件中存放数据库连接字符串。
// Some random controller file
const port = process.env.PORT || 3000;
mongoose.connect(process.env.MONGO_URI);
虽然很方便,但它存在三个严重问题。
- 没有类型安全。在 TypeScript 中,
process.env.PORT的类型为string | undefined。如果需要数字类型,每个调用处都必须手动解析它。 - 错误不会显式提示。当缺少
MONGO_URI时,进程在启动时不会崩溃,而是会在该行代码最终被执行时才出错,可能是在看似正常的部署数小时后。 - 没有统一的配置来源。新人无法获取应用所需的变量列表,他们唯一的办法就是在整个代码库中搜索
process.env。
一个统一的配置文件
解决办法是在业务逻辑中停止修改 process.env。创建一个名为 env.ts 的模块,在启动时读取一次环境变量,对其进行验证,然后导出一个强类型对象。其他所有模块都导入该对象。
以 TypeScript 为优先的架构验证库 Zod 负责执行验证工作。
安装它
npm install zod
编写架构定义
将该模块放在 src/config/env.ts 中,并详细描述环境应有的具体形态。
import dotenv from 'dotenv';
import { z } from 'zod';
// Load variables from .env file (if in development)
dotenv.config();
// 1. Define the schema
const envSchema = z.object({
PORT: z.string().transform(Number).default('3000'),
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
MONGO_URI: z.string(),
JWT_SECRET: z.string(),
JWT_EXPIRES_IN: z.string().default('1d'),
});
// 2. Parse the environment against the schema
const _env = envSchema.safeParse(process.env);
// 3. Fail fast if it's invalid
if (!_env.success) {
console.error('Invalid environment variables:', _env.error.format());
process.exit(1); // Crash the app immediately!
}
// 4. Export the typed, validated object
export const env = _env.data;
该模块的功能
- 它用于声明各种期望值。
MONGO_URI是必填的字符串。PORT也以字符串形式提供,会通过.transform(Number)进行转换,默认值为3000。NODE_ENV必须为三个允许的值之一。 - 它会快速检测错误。
safeParse会在文件被导入时立即执行,因此请将其放在入口文件的最顶部,无论是app.ts还是server.ts。如果缺少任何必填变量或变量格式不正确,系统会报告失败,错误信息会明确指出问题所在,同时process.exit(1)会在处理请求之前终止进程。 - 它会导出一个带类型的对象。解析成功后,数据会携带Zod推断出的类型,因此
env.PORT已经是数字类型。
从那时起,任何文件都可以导入 env 并加以使用。
您将获得的好处
- 自动补全功能。在编辑器中输入
env.即可列出所有可用的变量。 - 无需零散的备用方案。控制器中不再出现
|| "default_value"这类写法,因为默认值已存储在架构中。 - 内置文档。架构文件详细列出了应用程序运行所需的一切,同时也能解答新手关于哪些内容应放在
.env文件中的疑问。
现在,如果缺少密钥,系统会在启动时几秒钟内立即阻止部署,并给出明确提示,而不会等到一小时后才由愤怒的用户反映问题。