Validação com Zod & env-schema
Zod valida process.env no boot do Node.js para que deploys mal configurados falhem antes de aceitar tráfego. Um único schema fornece verificações em tempo de execução e tipos TypeScript sem desvios.
Busque em todas as páginas da documentação
Zod valida process.env no boot do Node.js para que deploys mal configurados falhem antes de aceitar tráfego. Um único schema fornece verificações em tempo de execução e tipos TypeScript sem desvios.
Cartão de receita de referência rápida - pronto para copiar e colar.
import { z } from "zod";
const envSchema = z.object({
NODE_ENV: z.enum(["development", "test", "production"]),
PORT: z.coerce.number().int().min(1).max(65535).default(3000),
DATABASE_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
});
export type Env = z.infer<typeof envSchema>;
export const env = envSchema.parse(process.env);# CI: valida o arquivo .env.example
npx tsx -e "import { envSchema } from './src/env'; envSchema.parse(require('dotenv').config({path:'.env.example'}).parsed)"Quando usar isso:
undefined em tempo de execução.env.example// src/env.ts
import { z } from "zod";
const envSchema = z
.object({
NODE_ENV: z.enum(["development", "test", "production"]),
PORT: z.coerce.number().default(3000),
DATABASE_URL: z.string().url(),
REDIS_URL: z.string().url().optional(),
CACHE_TTL_SECONDS: z.coerce.number().positive().default(300),
STRIPE_SECRET_KEY: z.string().startsWith("sk_"),
LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"),
OTEL_EXPORTER_OTLP_ENDPOINT: z.string().url().optional(),
})
.refine(
(e) => e.NODE_ENV !== "production" || e.OTEL_EXPORTER_OTLP_ENDPOINT,
{ message: "OTEL_EXPORTER_OTLP_ENDPOINT é obrigatório em produção", path: ["OTEL_EXPORTER_OTLP_ENDPOINT"] }
);
export type Env = z.infer<typeof envSchema>;
function formatZodError(error: z.ZodError): string {
return error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("\n");
}
export function loadEnv(source: NodeJS.ProcessEnv = process.env): Env {
const result = envSchema.safeParse(source);
if (!result.success) {
console.error("Configuração de ambiente inválida:\n" + formatZodError(result.error));
process.exit(1);
}
return result.data;
}
export const env = loadEnv();
// src/main.ts
import { env } from "./env";
import express from "express";
const app = express();
app.get("/health", (_req, res) => res.json({ ok: true, env: env.NODE_ENV }));
app.listen(env.PORT);O que isso demonstra:
safeParse + process.exit(1) fornece saída de erro amigável ao operadorz.coerce.number() lida com portas de string do Kubernetes env.refine impõe requisitos exclusivos de produçãoz.infer exporta Env para tipagem de injeção de dependênciaparse lança ZodError; safeParse retorna { success, data | error }loadEnv()| Padrão | Caso de Uso |
|---|---|
z.coerce.number() | PORT, tamanhos de pool, timeouts |
z.coerce.boolean() | Raro - prefira enum "true"/"false" para clareza |
.default() | Padrões sensatos de desenvolvimento; omita na documentação de produção |
.optional() | Integrações verdadeiramente opcionais |
.transform() | Analisa blobs de env JSON |
.refine() | Regras entre campos |
// Blob de configuração JSON em uma variável de ambiente (use com moderação)
const featureFlagsSchema = z
.string()
.default("{}")
.transform((s, ctx) => {
try {
return JSON.parse(s) as Record<string, boolean>;
} catch {
ctx.addIssue({ code: "custom", message: "FEATURE_FLAGS_JSON deve ser JSON válido" });
return z.NEVER;
}
});O pacote env-schema integra-se com Fastify e JSON Schema. Zod é mais comum em bases de código Express/Nest com foco em TypeScript e compõe com schemas de corpo de requisição.
// Passe Env para fábricas - nunca passe o process.env completo
export function createDbPool(cfg: Pick<Env, "DATABASE_URL">) {
return new Pool({ connectionString: cfg.DATABASE_URL });
}env.ts sair em variáveis ausentes. Correção: loadEnv(testEnv) com um helper reset ou vi.stubEnv..default("changeme") em JWT_SECRET. Correção: Sem padrões em segredos; falhe se ausente em produção.console.log(env) vaza segredos. Correção: Registre apenas chaves ou use um serializador com redação.dbEnv, authEnv) e mescle com .merge()..env.example - O arquivo de exemplo se desvia do schema. Correção: O job de CI analisa .env.example através do schema.| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
env-schema + JSON Schema | Projetos nativos do Fastify | Você já padroniza em Zod na fronteira HTTP |
convict | Configuração hierárquica com documentação | Serviços simples de 10 vars |
if (!process.env.X) manual | Scripts de 2 vars | APIs de produção |
dotenv-safe | Aplicação de chaves .env localmente | Produção (a plataforma injeta env) |
Use safeParse na entrada da aplicação quando quiser logs formatados antes de sair. Qualquer um é bom se um manipulador global formatar ZodError.
Passe um objeto de fixture para loadEnv({ DATABASE_URL: "postgres://..." }) e reinicie a configuração em cache entre os testes.
Sim. Valide process.env em ConfigModule.forRoot({ validate }) usando o mesmo schema Zod.
Use .refine em NODE_ENV === "production" ou uniões discriminadas em NODE_ENV.
Não integrado. Mantenha .env.example manualmente ou crie um script a partir de metadados do schema que você adiciona como .describe().
Zod não ecoa valores por padrão nas issues. Evite imprimir result.error.flatten().fieldErrors junto com dumps brutos de process.env.
Mesmo schema, valores diferentes. Campos opcionais podem diferir se .refine codificar regras específicas do ambiente.
Codifique em Base64 no env ou use montagem de arquivo do gerenciador de segredos; Zod valida a presença e o comprimento mínimo, não o formato de novas linhas.
Boolean("false") é true em JavaScript. Prefira z.enum(["true","false"]).transform(v => v === "true").
Quando as chaves rotacionam semanalmente ou a conformidade proíbe variáveis de ambiente em manifestos k8s. Zod ainda valida os valores resolvidos após a busca.
Versões da Stack: Esta página foi escrita para Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, e NestJS 11.
Revisado por Chris St. John·Última atualização: 16 de jul. de 2026