Skip to content

使用Zod在项目启动时检查环境变量

Published: at 09:10

环境变量是设置应用参数和各种密钥的常见方式,但很多时候我们不一定能准确的提供所有所需的环境变量,而由于 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> {}
  }
}

扩展全局类型定义