Fumadocs + Code Hike
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 dotenv
npm 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
# Application
NODE_ENV=development
PORT=3000
APP_NAME=IngenioLab API
APP_VERSION=1.0.0
# Database
DATABASE_URL=postgresql://user:password@localhost:5432/database_name
# Authentication
JWT_SECRET=your-super-secret-jwt-key-here
JWT_EXPIRES_IN=24h
BCRYPT_ROUNDS=12
# AWS Services
AWS_ACCESS_KEY_ID=your-aws-access-key
AWS_SECRET_ACCESS_KEY=your-aws-secret-key
AWS_REGION=us-east-1
S3_BUCKET=your-bucket-name
# Redis
REDIS_URL=redis://localhost:6379
REDIS_PASSWORD=
# Email Service
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your-email@gmail.com
SMTP_PASS=your-app-password
# External APIs
PAYMENT_API_KEY=your-payment-api-key
ANALYTICS_API_KEY=your-analytics-key
# Monitoring
SENTRY_DSN=https://your-sentry-dsn
LOG_LEVEL=info
# Development Only
DEV_ADMIN_EMAIL=admin@example.com
DEV_ADMIN_PASSWORD=admin123

3. Configuração centralizada:

// src/utils/env.ts
import 'dotenv/config' // Carrega automaticamente as variáveis
import { z } from 'zod'
// Schema de validação das variáveis de ambiente
const envSchema = z.object({
// Application
NODE_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'),
// Database
DATABASE_URL: z.string().url(),
// Authentication
JWT_SECRET: z.string().min(32),
JWT_EXPIRES_IN: z.string().default('24h'),
BCRYPT_ROUNDS: z.coerce.number().int().min(10).max(15).default(12),
// AWS
AWS_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(),
// Redis
REDIS_URL: z.string().url().optional(),
REDIS_PASSWORD: z.string().optional(),
// Email
SMTP_HOST: z.string().optional(),
SMTP_PORT: z.coerce.number().int().optional(),
SMTP_USER: z.string().optional(),
SMTP_PASS: z.string().optional(),
// External APIs
PAYMENT_API_KEY: z.string().optional(),
ANALYTICS_API_KEY: z.string().optional(),
// Monitoring
SENTRY_DSN: z.string().url().optional(),
LOG_LEVEL: z.enum(['trace', 'debug', 'info', 'warn', 'error', 'fatal']).default('info'),
// Development
DEV_ADMIN_EMAIL: z.string().email().optional(),
DEV_ADMIN_PASSWORD: z.string().optional(),
})
// Validar e exportar variáveis tipadas
function 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 TypeScript
export type Environment = z.infer<typeof envSchema>
// Helpers
export 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.ts
import { env } from '../utils/env'
export const databaseConfig = {
url: env.DATABASE_URL,
// Pool de conexões baseado no ambiente
connectionPool: {
min: env.NODE_ENV === 'production' ? 10 : 2,
max: env.NODE_ENV === 'production' ? 50 : 5,
idle: 10000,
acquire: 30000,
},
// SSL em produção
ssl: env.NODE_ENV === 'production' ? {
require: true,
rejectUnauthorized: false
} : false,
// Logging
logging: env.NODE_ENV === 'development'
}

Padrões de Configuração por Serviço

1. AWS Configuration:

// src/config/aws.ts
import { 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á configurado
export const isAwsConfigured = Boolean(
env.AWS_ACCESS_KEY_ID &&
env.AWS_SECRET_ACCESS_KEY &&
env.S3_BUCKET
)

2. Email Configuration:

// src/config/email.ts
import { 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 outros
auth: 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.ts
import { env } from '../utils/env'
export const redisConfig = env.REDIS_URL ? {
url: env.REDIS_URL,
password: env.REDIS_PASSWORD,
retryDelayOnFailover: 100,
enableReadyCheck: false,
maxRetriesPerRequest: null,
} : null
export const isRedisConfigured = Boolean(env.REDIS_URL)
// Cache TTL baseado no ambiente
export const cacheTTL = {
short: env.NODE_ENV === 'development' ? 60 : 300, // 1min dev, 5min prod
medium: env.NODE_ENV === 'development' ? 300 : 1800, // 5min dev, 30min prod
long: env.NODE_ENV === 'development' ? 600 : 3600, // 10min dev, 1h prod
}

Gerenciamento de Múltiplos Ambientes

1. Script de carregamento inteligente:

// src/utils/loadEnv.ts
import 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 prioridade
const 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 existe
const 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 importar
loadEnvironment()

2. Uso no entry point:

// src/server.ts
import './utils/loadEnv' // Carregar antes de tudo
import { 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.ts
import { 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ásicas
console.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()
// Database
console.log('🗄️ Database:')
console.log(` ${env.DATABASE_URL ? '✅' : '❌'} DATABASE_URL: ${env.DATABASE_URL ? 'Configurado' : 'Não configurado'}`)
console.log()
// Autenticação
console.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 opcionais
console.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 desenvolvimento
if (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íveis
logs/
*.log
# IDE settings que podem vazar informações
.vscode/settings.json
.idea/

2. Sanitização de variáveis:

// src/utils/sanitize.ts
export 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ção
console.log('Config loaded:', sanitizeEnvForLogging(env))

3. Rotação de secrets:

// src/utils/secrets.ts
import { env } from './env'
export class SecretsManager {
static validateSecretRotation() {
// Verificar se secrets não são valores padrão
const 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 dias
return 0
}
static shouldRotateSecret(secretName: string, maxAgeDays: number = 90): boolean {
return this.getSecretAge(secretName) > maxAgeDays
}
}

Docker e Deploy

1. Dockerfile com multi-stage:

# Dockerfile
FROM node:18-alpine AS dependencies
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:18-alpine AS builder
WORKDIR /app
COPY . .
COPY --from=dependencies /app/node_modules ./node_modules
RUN npm run build
FROM node:18-alpine AS runner
WORKDIR /app
# Criar usuário não-root
RUN addgroup -g 1001 -S nodejs && adduser -S nextjs -u 1001
# Copiar arquivos necessários
COPY --from=builder /app/dist ./dist
COPY --from=dependencies /app/node_modules ./node_modules
COPY package*.json ./
# Variáveis de ambiente de produção
ENV NODE_ENV=production
ENV PORT=3000
USER nextjs
EXPOSE 3000
CMD ["node", "dist/server.js"]

2. docker-compose para desenvolvimento:

# docker-compose.yml
version: '3.8'
services:
api:
build: .
ports:
- "3000:3000"
env_file:
- .env.development
environment:
- NODE_ENV=development
volumes:
- .:/app
- /app/node_modules
depends_on:
- postgres
- redis
postgres:
image: postgres:15-alpine
environment:
POSTGRES_USER: ${DB_USER:-postgres}
POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres}
POSTGRES_DB: ${DB_NAME:-ingeniolab}
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
ports:
- "6379:6379"
command: redis-server --requirepass ${REDIS_PASSWORD:-redis123}
volumes:
postgres_data: