Variáveis de ambiente permitem configurar uma aplicação Node.js sem alterar o código. Elas são usadas para portas, URLs de serviços, feature flags, níveis de log e referências a secrets. A API principal é process.env, mas os valores chegam como strings e precisam ser validados antes de iniciar o servidor.
Uma configuração ausente ou inválida deve falhar no startup, não aparecer horas depois em uma rota rara. Centralizar parsing e validação reduz bugs e diferenças entre desenvolvimento, teste e produção.
Lendo process.env
const port = process.env.PORT;
const databaseUrl = process.env.DATABASE_URL;Mesmo números e booleanos são strings. A ausência produz undefined.
Booleanos
Este código está errado:
const debug = Boolean(process.env.DEBUG);Boolean('false') é true. Faça parsing explícito:
function parseBoolean(value, defaultValue = false) {
if (value === undefined) return defaultValue;
if (value === 'true') return true;
if (value === 'false') return false;
throw new Error(`Booleano inválido: ${value}`);
}Números
function parseInteger(name, value, {
min,
max,
defaultValue,
} = {}) {
if (value === undefined) {
if (defaultValue !== undefined) return defaultValue;
throw new Error(`${name} é obrigatória`);
}
const parsed = Number(value);
if (!Number.isInteger(parsed)) {
throw new Error(`${name} deve ser inteiro`);
}
if (min !== undefined && parsed < min) throw new Error(`${name} abaixo do mínimo`);
if (max !== undefined && parsed > max) throw new Error(`${name} acima do máximo`);
return parsed;
}Configuração centralizada
export const config = Object.freeze({
nodeEnv: process.env.NODE_ENV || 'development',
port: parseInteger('PORT', process.env.PORT, {
min: 1,
max: 65535,
defaultValue: 3000,
}),
logLevel: process.env.LOG_LEVEL || 'info',
databaseUrl: requireValue('DATABASE_URL'),
featureX: parseBoolean(process.env.FEATURE_X, false),
});Importe config em vez de acessar process.env espalhado pelo projeto.
Fail fast
Carregue e valide antes de abrir porta:
import { config } from './config.js';
await connectDatabase(config.databaseUrl);
server.listen(config.port);Se a configuração falhar, registre quais nomes estão inválidos sem mostrar valores sensíveis.
.env
Um arquivo pode conter:
PORT=3000
LOG_LEVEL=debug
DATABASE_URL=postgres://localhost/app
FEATURE_X=falseUse em desenvolvimento e testes locais. Não versione arquivos com secrets.
Carregamento nativo
Versões modernas do Node.js oferecem suporte a arquivos dotenv por opções de linha de comando e APIs relacionadas. Verifique a versão mínima do projeto e use uma única estratégia.
node --env-file=.env dist/server.jsNão misture loaders que aplicam precedência diferente sem documentar.
Precedência
Defina uma ordem, por exemplo:
- variáveis fornecidas pela plataforma;
- arquivo específico do ambiente;
- arquivo local;
- defaults seguros.
Produção deve preferir configuração da plataforma. Um arquivo não deve sobrescrever secret injetado inesperadamente.
.env.example
Versione apenas nomes e exemplos não sensíveis:
PORT=3000
LOG_LEVEL=info
DATABASE_URL=
FEATURE_X=falseDocumente obrigatoriedade, formato e propósito.
NODE_ENV
Não use NODE_ENV para representar dezenas de decisões de negócio. Ele costuma distinguir desenvolvimento, teste e produção. Feature flags e URLs devem ter variáveis próprias.
Enums
function parseEnum(name, value, allowed) {
if (!allowed.includes(value)) {
throw new Error(`${name} deve ser: ${allowed.join(', ')}`);
}
return value;
}
const logLevel = parseEnum(
'LOG_LEVEL',
process.env.LOG_LEVEL || 'info',
['trace', 'debug', 'info', 'warn', 'error', 'fatal'],
);Listas
function parseCsv(value) {
if (!value) return [];
return value.split(',').map((item) => item.trim()).filter(Boolean);
}Se valores podem conter vírgulas, use JSON ou outra codificação.
JSON em variável
const options = JSON.parse(process.env.OPTIONS_JSON || '{}');JSON grande em env é difícil de operar e pode aparecer em ferramentas do sistema. Prefira arquivo montado ou serviço de configuração.
URLs
function parseUrl(name, value) {
try {
return new URL(requireValue(name, value));
} catch (error) {
throw new Error(`${name} deve ser uma URL válida`, { cause: error });
}
}Valide protocolo e hostname permitido quando necessário.
Durações
Evite valores ambíguos como TIMEOUT=5. Use unidade no nome:
HTTP_TIMEOUT_MS=5000Tamanhos
Use nomes como MAX_BODY_BYTES. Isso reduz conversões erradas.
Defaults
Defaults são adequados para porta local e log level. Não use default de produção para credenciais, URL de banco ou chave criptográfica.
Mutação
Evite alterar process.env durante execução. Configuração deve ser carregada uma vez e congelada. Mudanças dinâmicas exigem mecanismo explícito.
Testes
Importar config no topo pode dificultar testes porque módulos ficam em cache. Exponha uma função:
export function loadConfig(env = process.env) {
return Object.freeze({
port: parseInteger('PORT', env.PORT, { defaultValue: 3000 }),
databaseUrl: requireValue('DATABASE_URL', env.DATABASE_URL),
});
}Testes passam objetos independentes.
Não compartilhar process.env entre testes
Se precisar modificar, salve e restaure no finally. Prefira injeção.
Workers
Worker Threads recebem uma cópia do ambiente conforme a criação e opções. Não dependa de mutações posteriores. Passe configuração mínima por workerData ou mensagem.
Processos filhos
spawn('node', ['job.js'], {
env: {
NODE_ENV: config.nodeEnv,
JOB_ID: jobId,
},
});Não passe ...process.env automaticamente se o filho não precisa de secrets.
Containers
Variáveis podem ser definidas no deployment, Compose ou runtime. Não use ENV SECRET=... no Dockerfile, pois pode ficar no histórico da imagem.
Kubernetes ConfigMap
ConfigMap é apropriado para configuração não sensível. Secrets do Kubernetes exigem controle adicional; base64 não é criptografia.
Arquivos montados
Configuração complexa pode ser montada como arquivo. A aplicação lê e valida o conteúdo. Isso também facilita rotação de certificados.
Configuração dinâmica
Feature flags e parâmetros que mudam em runtime podem usar serviço dedicado. Implemente cache, fallback, validação e auditoria.
Feature flags
Variável de ambiente exige novo deploy ou restart para mudar. É boa para flags de infraestrutura, não para experimentos frequentes.
Secrets
Variáveis de ambiente podem carregar secrets, mas são visíveis a processos, dumps e ferramentas conforme a plataforma. Prefira identidade da workload e secret manager quando possível.
Logs
Nunca registre o objeto config completo. Crie uma versão sanitizada:
logger.info({
nodeEnv: config.nodeEnv,
port: config.port,
logLevel: config.logLevel,
databaseConfigured: Boolean(config.databaseUrl),
}, 'Configuração carregada');Diagnóstico
Em endpoint interno, mostre apenas nomes, flags não sensíveis e versão. Não exponha valores de secrets.
Schema
Bibliotecas de validação podem definir um schema, transformar tipos e produzir mensagens claras. Ainda assim, revise redaction dos erros.
Config drift
Duas réplicas podem iniciar com valores diferentes. Inclua um hash de configuração não sensível nos logs e métricas para detectar divergência.
Immutable infrastructure
Promova o mesmo artefato entre ambientes e altere somente configuração externa. Isso reduz rebuilds específicos e diferenças de código.
Observabilidade
Registre versão do aplicativo, ambiente, região e flags permitidas como atributos de recurso. Não use valores secretos.
Erros comuns
- usar Boolean em string;
- não validar números;
- acessar process.env em todo lugar;
- default para secret;
- versionar .env;
- passar todo ambiente a filhos;
- registrar config completo;
- usar NODE_ENV para tudo;
- misturar precedências;
- falhar somente na primeira requisição.
Fluxo recomendado
Carregue uma vez, valide tipos e falhe antes de abrir o servidor. Centralize configuração, documente .env.example e não registre valores sensíveis. Combine com Pino, Tratamento de Erros, Health Checks e Graceful Shutdown.
Consulte a documentação oficial de variáveis de ambiente e a referência de –env-file.



