环境变量是设置应用参数和各种密钥的常见方式,但很多时候我们不一定能准确的提供所有所需的环境变量,而由于 JavaScript 的解释型语言的特性,环境变量的缺失往往会在代码运行到对应逻辑时才被发现。
显然,相比在把项目运行甚至部署后才发现环境变量缺失或者有误,更好的办法是在应用启动前就进行检查。
比较直观的做法是,应用启动前对所需的各种环境变量进行逐一判断并进行提示,直接使用条件语句判断或者 tiny-invariant
之类的工具都可以快速实现。
这种方式在环境变量变多时重复代码和逻辑也会显著变多,可读性明显下降,这时候,或者一开始就使用 Zod 检查环境变量会是更好的方案。
TOC
使用 zod schema 描述环境变量结构
使用 Zod 可以轻松直观地描述项目依赖的环境变量的结构
import { z } from 'zod';
const envSchema = z.object({
NODE_ENV: z.enum(['production', 'development', 'test'] as const),
STRIPE_SECRET: z.string().startsWith('sk_'),
OPTIONAL_KEY_WITH_DEFAULT: z.string().default('DEFAULT_VALUE'),
});
如果有进阶的校验需求,可以使用 refine
等方式进行定义。例如,我们期望非 production
环境不使用 Stripe 的正式 secret,则可以这样写:
const envSchema = z
.object({
NODE_ENV: z.enum(['production', 'development', 'test'] as const),
STRIPE_SECRET: z.string().startsWith('sk_'),
OPTIONAL_KEY_WITH_DEFAULT: z.string().default('DEFAULT_VALUE'),
})
.refine(
env =>
env.NODE_ENV !== 'production' && env.STRIPE_SECRET.startsWith('sk_live_'),
{
message: 'Should not use Stripe live key in non-production environment',
}
);
使用 schema 解析校验 env
在定义好 schema 后,还需要在应用启动前使用 envSchema
解析 process.env
。
由于 Zod 的 parse()
抛出的错误结构比较复杂不够直观,所以示例代码使用 safeParse()
并对错误进行了处理,以使得错误信息简洁一些。
// 在应用启动前调用即可
export function checkEnv() {
const parsed = envSchema.safeParse(process.env);
if (!parsed.success) {
console.error(
'❌ Invalid environment variables:',
parsed.error.flatten().fieldErrors
);
throw new Error('Invalid environment variables');
}
}
环境变量未通过校验时会报错。下图是没有设置任何相关环境变量的情况:
使用 schema 扩展全局类型定义
经过解析处理后,我们可以确认环境变量符合 envSchema
的定义,因此可以使用 envSchema
扩展全局类型定义,从而在代码中得到对应的类型提示
declare global {
namespace NodeJS {
interface ProcessEnv extends z.infer<typeof envSchema> {}
}
}