Backend
dotenv
Gerenciamento de variáveis de ambiente para configuração segura de aplicações
O que é dotenv?
dotenv é uma biblioteca que carrega variáveis de ambiente de um arquivo .env para process.env no Node.js. É fundamental para separar configuração do código, mantendo credenciais e configurações sensíveis fora do versionamento.
Por que utilizamos dotenv na IngenioLab?
- Segurança: Mantém credenciais fora do código fonte
- Flexibilidade: Diferentes configurações por ambiente
- Simplicidade: Sintaxe simples de chave=valor
- Padrão da indústria: Amplamente adotado pela comunidade
- 12-Factor App: Segue princípios de aplicações modernas
- Zero config: Funciona sem configuração adicional
Instalação
npm install dotenvnpm install -D @types/dotenv # Para TypeScript
Configuração Base IngenioLab
1. Estrutura de arquivos de ambiente:
projeto/├── .env # Configurações locais (não versionar)├── .env.example # Template com chaves (versionar)├── .env.development # Ambiente de desenvolvimento├── .env.staging # Ambiente de staging├── .env.production # Ambiente de produção (não versionar)└── .gitignore # Ignorar arquivos .env sensíveis
2. .env.example (template):
# .env.example - Template para outros desenvolvedores# Copiar para .env e preencher com valores reais# ApplicationNODE_ENV=developmentPORT=3000APP_NAME=IngenioLab APIAPP_VERSION=1.0.0# DatabaseDATABASE_URL=postgresql://user:password@localhost:5432/database_name# AuthenticationJWT_SECRET=your-super-secret-jwt-key-hereJWT_EXPIRES_IN=24hBCRYPT_ROUNDS=12# AWS ServicesAWS_ACCESS_KEY_ID=your-aws-access-keyAWS_SECRET_ACCESS_KEY=your-aws-secret-keyAWS_REGION=us-east-1S3_BUCKET=your-bucket-name# RedisREDIS_URL=redis://localhost:6379REDIS_PASSWORD=# Email ServiceSMTP_HOST=smtp.gmail.comSMTP_PORT=587SMTP_USER=your-email@gmail.comSMTP_PASS=your-app-password# External APIsPAYMENT_API_KEY=your-payment-api-keyANALYTICS_API_KEY=your-analytics-key# MonitoringSENTRY_DSN=https://your-sentry-dsnLOG_LEVEL=info# Development OnlyDEV_ADMIN_EMAIL=admin@example.comDEV_ADMIN_PASSWORD=admin123
3. Configuração centralizada:
// src/utils/env.tsimport 'dotenv/config' // Carrega automaticamente as variáveisimport { z } from 'zod'// Schema de validação das variáveis de ambienteconst envSchema = z.object({// ApplicationNODE_ENV: z.enum(['development', 'staging', 'production']).default('development'),PORT: z.coerce.number().int().min(1000).max(65535).default(3000),APP_NAME: z.string().default('IngenioLab API'),APP_VERSION: z.string().default('1.0.0'),// DatabaseDATABASE_URL: z.string().url(),// AuthenticationJWT_SECRET: z.string().min(32),JWT_EXPIRES_IN: z.string().default('24h'),BCRYPT_ROUNDS: z.coerce.number().int().min(10).max(15).default(12),// AWSAWS_ACCESS_KEY_ID: z.string().optional(),AWS_SECRET_ACCESS_KEY: z.string().optional(),AWS_REGION: z.string().default('us-east-1'),S3_BUCKET: z.string().optional(),// RedisREDIS_URL: z.string().url().optional(),REDIS_PASSWORD: z.string().optional(),SMTP_HOST: z.string().optional(),SMTP_PORT: z.coerce.number().int().optional(),SMTP_USER: z.string().optional(),SMTP_PASS: z.string().optional(),// External APIsPAYMENT_API_KEY: z.string().optional(),ANALYTICS_API_KEY: z.string().optional(),// MonitoringSENTRY_DSN: z.string().url().optional(),LOG_LEVEL: z.enum(['trace', 'debug', 'info', 'warn', 'error', 'fatal']).default('info'),// DevelopmentDEV_ADMIN_EMAIL: z.string().email().optional(),DEV_ADMIN_PASSWORD: z.string().optional(),})// Validar e exportar variáveis tipadasfunction validateEnv() {try {return envSchema.parse(process.env)} catch (error) {console.error('❌ Configuração de ambiente inválida:')if (error instanceof z.ZodError) {error.errors.forEach(err => {console.error(` - ${err.path.join('.')}: ${err.message}`)})}console.error('\n💡 Verifique o arquivo .env e compare com .env.example')process.exit(1)}}export const env = validateEnv()// Type export para TypeScriptexport type Environment = z.infer<typeof envSchema>// Helpersexport const isDevelopment = env.NODE_ENV === 'development'export const isProduction = env.NODE_ENV === 'production'export const isStaging = env.NODE_ENV === 'staging'
4. Configuração condicional por ambiente:
// src/config/database.tsimport { env } from '../utils/env'export const databaseConfig = {url: env.DATABASE_URL,// Pool de conexões baseado no ambienteconnectionPool: {min: env.NODE_ENV === 'production' ? 10 : 2,max: env.NODE_ENV === 'production' ? 50 : 5,idle: 10000,acquire: 30000,},// SSL em produçãossl: env.NODE_ENV === 'production' ? {require: true,rejectUnauthorized: false} : false,// Logginglogging: env.NODE_ENV === 'development'}
Padrões de Configuração por Serviço
1. AWS Configuration:
// src/config/aws.tsimport { env } from '../utils/env'export const awsConfig = {region: env.AWS_REGION,credentials: env.AWS_ACCESS_KEY_ID && env.AWS_SECRET_ACCESS_KEY ? {accessKeyId: env.AWS_ACCESS_KEY_ID,secretAccessKey: env.AWS_SECRET_ACCESS_KEY,} : undefined,}export const s3Config = {...awsConfig,bucket: env.S3_BUCKET || 'default-bucket',signedUrlExpires: 3600, // 1 hora}// Verificar se AWS está configuradoexport const isAwsConfigured = Boolean(env.AWS_ACCESS_KEY_ID &&env.AWS_SECRET_ACCESS_KEY &&env.S3_BUCKET)
2. Email Configuration:
// src/config/email.tsimport { env } from '../utils/env'export const emailConfig = {smtp: {host: env.SMTP_HOST,port: env.SMTP_PORT || 587,secure: env.SMTP_PORT === 465, // true para port 465, false para outrosauth: env.SMTP_USER && env.SMTP_PASS ? {user: env.SMTP_USER,pass: env.SMTP_PASS,} : undefined,},defaults: {from: env.SMTP_USER || 'noreply@ingeniolab.com',replyTo: 'contato@ingeniolab.com',}}export const isEmailConfigured = Boolean(env.SMTP_HOST &&env.SMTP_USER &&env.SMTP_PASS)
3. Redis Configuration:
// src/config/redis.tsimport { env } from '../utils/env'export const redisConfig = env.REDIS_URL ? {url: env.REDIS_URL,password: env.REDIS_PASSWORD,retryDelayOnFailover: 100,enableReadyCheck: false,maxRetriesPerRequest: null,} : nullexport const isRedisConfigured = Boolean(env.REDIS_URL)// Cache TTL baseado no ambienteexport const cacheTTL = {short: env.NODE_ENV === 'development' ? 60 : 300, // 1min dev, 5min prodmedium: env.NODE_ENV === 'development' ? 300 : 1800, // 5min dev, 30min prodlong: env.NODE_ENV === 'development' ? 600 : 3600, // 10min dev, 1h prod}
Gerenciamento de Múltiplos Ambientes
1. Script de carregamento inteligente:
// src/utils/loadEnv.tsimport dotenv from 'dotenv'import path from 'node:path'import fs from 'node:fs'export function loadEnvironment() {const nodeEnv = process.env.NODE_ENV || 'development'const envPath = path.resolve(process.cwd())// Lista de arquivos em ordem de prioridadeconst envFiles = [`.env.${nodeEnv}.local`, // Mais específico`.env.local`, // Local overrides`.env.${nodeEnv}`, // Ambiente específico'.env' // Padrão]console.log(`🌍 Carregando configuração para ambiente: ${nodeEnv}`)for (const file of envFiles) {const filePath = path.join(envPath, file)if (fs.existsSync(filePath)) {console.log(`📁 Carregando: ${file}`)dotenv.config({ path: filePath, override: false })}}// Verificar se arquivo .env existeconst mainEnvPath = path.join(envPath, '.env')if (!fs.existsSync(mainEnvPath)) {console.warn('⚠️ Arquivo .env não encontrado!')console.log('💡 Copie .env.example para .env e configure as variáveis')}}// Carregar ao importarloadEnvironment()
2. Uso no entry point:
// src/server.tsimport './utils/loadEnv' // Carregar antes de tudoimport { build } from './app'import { env } from './utils/env'const start = async () => {console.log(`🚀 Iniciando ${env.APP_NAME} v${env.APP_VERSION}`)console.log(`📍 Ambiente: ${env.NODE_ENV}`)console.log(`🌐 Porta: ${env.PORT}`)try {const app = await build({logger: {level: env.LOG_LEVEL,prettyPrint: env.NODE_ENV === 'development'}})await app.listen({port: env.PORT,host: '0.0.0.0'})console.log(`✅ Servidor rodando na porta ${env.PORT}`)} catch (err) {console.error('❌ Erro ao iniciar servidor:', err)process.exit(1)}}start()
Validação e Debugging
1. Comando de verificação:
// scripts/check-env.tsimport { env, isDevelopment } from '../src/utils/env'import { isAwsConfigured } from '../src/config/aws'import { isEmailConfigured } from '../src/config/email'import { isRedisConfigured } from '../src/config/redis'function checkEnvironment() {console.log('🔍 Verificando configuração do ambiente...\n')// Informações básicasconsole.log('📋 Configuração básica:')console.log(` ✅ NODE_ENV: ${env.NODE_ENV}`)console.log(` ✅ PORT: ${env.PORT}`)console.log(` ✅ APP_NAME: ${env.APP_NAME}`)console.log()// Databaseconsole.log('🗄️ Database:')console.log(` ${env.DATABASE_URL ? '✅' : '❌'} DATABASE_URL: ${env.DATABASE_URL ? 'Configurado' : 'Não configurado'}`)console.log()// Autenticaçãoconsole.log('🔐 Autenticação:')console.log(` ${env.JWT_SECRET ? '✅' : '❌'} JWT_SECRET: ${env.JWT_SECRET ? 'Configurado' : 'Não configurado'}`)console.log(` ✅ BCRYPT_ROUNDS: ${env.BCRYPT_ROUNDS}`)console.log()// Serviços opcionaisconsole.log('☁️ Serviços externos:')console.log(` ${isAwsConfigured ? '✅' : '⚠️ '} AWS: ${isAwsConfigured ? 'Configurado' : 'Não configurado'}`)console.log(` ${isEmailConfigured ? '✅' : '⚠️ '} Email: ${isEmailConfigured ? 'Configurado' : 'Não configurado'}`)console.log(` ${isRedisConfigured ? '✅' : '⚠️ '} Redis: ${isRedisConfigured ? 'Configurado' : 'Não configurado'}`)console.log()// Avisos para desenvolvimentoif (isDevelopment) {console.log('🛠️ Desenvolvimento:')if (!env.DEV_ADMIN_EMAIL) {console.log(' ⚠️ DEV_ADMIN_EMAIL não configurado')}if (!env.DEV_ADMIN_PASSWORD) {console.log(' ⚠️ DEV_ADMIN_PASSWORD não configurado')}}console.log('✅ Verificação concluída!')}checkEnvironment()
2. Package.json scripts:
{"scripts": {"env:check": "tsx scripts/check-env.ts","env:example": "cp .env .env.example && echo 'Arquivo .env.example atualizado'","dev": "npm run env:check && tsx watch src/server.ts","build": "npm run env:check && tsc","start": "NODE_ENV=production node dist/server.js"}}
Segurança e Boas Práticas
1. .gitignore apropriado:
# Environment variables.env.env.local.env.production.env.staging# Manter apenas o template!.env.example# Logs que podem conter dados sensíveislogs/*.log# IDE settings que podem vazar informações.vscode/settings.json.idea/
2. Sanitização de variáveis:
// src/utils/sanitize.tsexport function sanitizeEnvForLogging(env: Record<string, any>) {const sensitive = ['password', 'secret', 'key', 'token', 'dsn']const sanitized = { ...env }Object.keys(sanitized).forEach(key => {const lowerKey = key.toLowerCase()const isSensitive = sensitive.some(keyword => lowerKey.includes(keyword))if (isSensitive && sanitized[key]) {sanitized[key] = '***REDACTED***'}})return sanitized}// Logging seguro da configuraçãoconsole.log('Config loaded:', sanitizeEnvForLogging(env))
3. Rotação de secrets:
// src/utils/secrets.tsimport { env } from './env'export class SecretsManager {static validateSecretRotation() {// Verificar se secrets não são valores padrãoconst defaultSecrets = ['your-super-secret-jwt-key-here','admin123','password123']if (defaultSecrets.includes(env.JWT_SECRET)) {throw new Error('JWT_SECRET ainda está com valor padrão! Altere para um valor seguro.')}if (env.DEV_ADMIN_PASSWORD === 'admin123' && env.NODE_ENV === 'production') {throw new Error('Senha de admin padrão em produção!')}}static getSecretAge(secretName: string): number {// Em um cenário real, isso consultaria um sistema de gerenciamento de secrets// Retornaria a idade do secret em diasreturn 0}static shouldRotateSecret(secretName: string, maxAgeDays: number = 90): boolean {return this.getSecretAge(secretName) > maxAgeDays}}
Docker e Deploy
1. Dockerfile com multi-stage:
# DockerfileFROM node:18-alpine AS dependenciesWORKDIR /appCOPY package*.json ./RUN npm ci --only=productionFROM node:18-alpine AS builderWORKDIR /appCOPY . .COPY --from=dependencies /app/node_modules ./node_modulesRUN npm run buildFROM node:18-alpine AS runnerWORKDIR /app# Criar usuário não-rootRUN addgroup -g 1001 -S nodejs && adduser -S nextjs -u 1001# Copiar arquivos necessáriosCOPY --from=builder /app/dist ./distCOPY --from=dependencies /app/node_modules ./node_modulesCOPY package*.json ./# Variáveis de ambiente de produçãoENV NODE_ENV=productionENV PORT=3000USER nextjsEXPOSE 3000CMD ["node", "dist/server.js"]
2. docker-compose para desenvolvimento:
# docker-compose.ymlversion: '3.8'services:api:build: .ports:- "3000:3000"env_file:- .env.developmentenvironment:- NODE_ENV=developmentvolumes:- .:/app- /app/node_modulesdepends_on:- postgres- redispostgres:image: postgres:15-alpineenvironment:POSTGRES_USER: ${DB_USER:-postgres}POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres}POSTGRES_DB: ${DB_NAME:-ingeniolab}ports:- "5432:5432"volumes:- postgres_data:/var/lib/postgresql/dataredis:image: redis:7-alpineports:- "6379:6379"command: redis-server --requirepass ${REDIS_PASSWORD:-redis123}volumes:postgres_data:
Links Úteis
- Documentação Oficial do dotenv
- 12-Factor App - Config
- Environment Variables Best Practices
- dotenv-expand - Para variáveis interpoladas
- Doppler - Gerenciamento avançado de secrets