跳转到内容

环境变量校验

基于 Zod 的环境变量验证工具,在应用启动时校验 process.env,确保配置正确。

为什么需要环境变量校验?

  • 快速失败:启动时就发现配置错误,而非运行时某个请求突然报错
  • 类型安全:校验后的环境变量有完整的 TypeScript 类型
  • 自动转换:字符串自动转为 number / boolean

基础用法

typescript
// config/env.ts
import { z } from 'zod'
import { validateEnv, zPort, zBoolEnv } from '@maxtan/nest-core'

const envSchema = z.object({
  NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
  PORT: zPort().default(3000),
  DATABASE_URL: z.string().url(),
  REDIS_HOST: z.string().default('localhost'),
  REDIS_PORT: zPort().default(6379),
  JWT_SECRET: z.string().min(16, 'JWT_SECRET 至少 16 位'),
  ENABLE_SWAGGER: zBoolEnv().default(false),
})

export const env = validateEnv(envSchema)
typescript
// main.ts
import { env } from './config/env'

async function bootstrap() {
  createLogger({ useConsole: env.NODE_ENV === 'development' })

  const app = await NestFactory.create(AppModule)
  await app.listen(env.PORT)  // number 类型,无需 parseInt
}

API

validateEnv(schema, options?)

参数类型说明
schemaZodTypeZod schema,定义环境变量结构
options.envSourceRecord环境变量来源,默认 process.env
options.onError'throw' | 'log'验证失败行为,默认 'throw'

错误输出格式

验证失败时抛出 EnvValidationError,错误信息清晰可读:

EnvValidationError: 环境变量验证失败:
  - DATABASE_URL: Required
  - JWT_SECRET: JWT_SECRET 至少 16 位
  - REDIS_PORT: Expected number, received nan

内置预处理器

zPort()

端口号预处理器,自动转换字符串 → 数字,范围 1-65535。

typescript
const schema = z.object({
  PORT: zPort().default(3000),
  REDIS_PORT: zPort().default(6379),
})

zBoolEnv()

布尔值环境变量预处理器。

输入值结果
'true' / '1' / 'yes'true
'false' / '0' / 'no' / ''false
typescript
const schema = z.object({
  ENABLE_SWAGGER: zBoolEnv().default(false),
  DEBUG: zBoolEnv().default(false),
})

zEnvStr()

必填字符串,自动 trim + 空字符串视为 undefined。

typescript
const schema = z.object({
  DATABASE_URL: zEnvStr(),
  JWT_SECRET: zEnvStr().pipe(z.string().min(16)),
})

zEnvNum(toInt?)

数字环境变量预处理器。

typescript
const schema = z.object({
  MAX_CONNECTIONS: zEnvNum(true).default(10),   // 整数
  RATE_LIMIT: zEnvNum().default(100.5),          // 浮点数
})

进阶用法

条件必填

typescript
const schema = z.object({
  NODE_ENV: z.enum(['development', 'production']).default('development'),
  SENTRY_DSN: z.string().optional(),
}).refine(
  (env) => env.NODE_ENV !== 'production' || !!env.SENTRY_DSN,
  { message: '生产环境必须配置 SENTRY_DSN', path: ['SENTRY_DSN'] }
)

仅打印错误不退出

typescript
const env = validateEnv(schema, { onError: 'log' })
// 验证失败时 console.error 打印错误,返回 null

自定义来源

typescript
// 测试时传入自定义环境变量
const env = validateEnv(schema, {
  envSource: {
    PORT: '4000',
    DATABASE_URL: 'mysql://test@localhost/test',
    JWT_SECRET: 'test-secret-key-1234567',
  }
})

最佳实践

  1. 集中定义:将环境变量 Schema 放在 config/env.ts 统一管理
  2. 服务启动最前面校验:在 main.tsimport 阶段就完成校验,确保快速失败
  3. 严格模式:生产环境使用默认 onError: 'throw',绝不允许带着错误配置启动
  4. 类型安全:将 validateEnv() 返回值导出为常量,全项目共享类型推导
  5. 条件必填:生产环境特有的配置(如 SENTRY_DSN)用 .refine() 校验
typescript
// config/env.ts — 推荐的统一配置文件
import { z } from 'zod'
import { validateEnv, zPort, zBoolEnv, zEnvStr } from '@maxtan/nest-core'

export const env = validateEnv(
  z.object({
    NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
    PORT: zPort().default(3000),
    DATABASE_URL: zEnvStr(),
    REDIS_HOST: z.string().default('localhost'),
    REDIS_PORT: zPort().default(6379),
    JWT_SECRET: zEnvStr().pipe(z.string().min(16)),
    ENABLE_SWAGGER: zBoolEnv().default(false),
  })
)

相关文档