Gestire .env e Variabili d'Ambiente in Node.js
Gestire .env e variabili d'ambiente in Node.js: dotenv, sicurezza e best practices. Guida completa per configurazioni sicure in sviluppo e produzione.
Cosa Sono le Variabili d'Ambiente
Le variabili d'ambiente sono coppie chiave-valore che configurano il comportamento delle applicazioni senza modificare il codice. Sono fondamentali per separare configurazione da codice, gestire secrets in modo sicuro e differenziare ambienti (sviluppo, staging, produzione). In Node.js, il pattern .env è diventato lo standard de facto.
Il File .env
Struttura Base
# .env - file di configurazione locale
# Commenti iniziano con #
# Database
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
DB_HOST=localhost
DB_PORT=5432
DB_USER=admin
DB_PASSWORD=secret123
# API Keys
API_KEY=sk_live_abcd1234567890
STRIPE_SECRET_KEY=sk_test_xyz
OPENAI_API_KEY=sk-abc123
# Configurazione app
NODE_ENV=development
PORT=3000
LOG_LEVEL=debug
# Feature flags
ENABLE_NEW_FEATURE=true
MAINTENANCE_MODE=false
# URLs
FRONTEND_URL=http://localhost:3000
API_URL=http://localhost:4000/api
Regole di Sintassi
# Stringhe semplici (senza quotes)
KEY=value
# Stringhe con spazi (richiedono quotes)
MESSAGE="Hello World"
GREETING='Welcome to the app'
# Multiline (con \n o quotes)
PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIIE..."
# Variabili con riferimento (alcuni parser supportano)
BASE_URL=https://api.example.com
FULL_URL=${BASE_URL}/v1
# Valori vuoti
EMPTY_VAR=
ALSO_EMPTY=""
Dotenv in Node.js
Setup Base
# Installazione
npm install dotenv
# Struttura progetto
project/
├── .env # Valori locali (gitignore!)
├── .env.example # Template per altri dev
├── .env.production # Prod values (opzionale)
└── src/
└── index.js
// src/index.js - Carica all'inizio dell'app
require('dotenv').config();
// oppure con ES modules
import 'dotenv/config';
// Accedi alle variabili
const port = process.env.PORT || 3000;
const dbUrl = process.env.DATABASE_URL;
console.log(`Server running on port ${port}`);
Configurazione Avanzata
// Specifica percorso file .env
require('dotenv').config({ path: '.env.local' });
// Carica più file in ordine
require('dotenv').config({ path: '.env' });
require('dotenv').config({ path: '.env.local', override: true });
// ES modules con import
import { config } from 'dotenv';
config({ path: '.env.production' });
// Accesso sicuro con fallback
const apiKey = process.env.API_KEY ?? 'default-key';
const port = parseInt(process.env.PORT, 10) || 3000;
Validazione Variabili
Con Zod
import { z } from 'zod';
const envSchema = z.object({
NODE_ENV: z.enum(['development', 'production', 'test']),
PORT: z.string().transform(Number).default('3000'),
DATABASE_URL: z.string().url(),
API_KEY: z.string().min(10),
ENABLE_FEATURE: z.string().transform(v => v === 'true').default('false'),
});
// Valida e esporta
export const env = envSchema.parse(process.env);
// TypeScript inferisce i tipi automaticamente
// env.PORT è number
// env.ENABLE_FEATURE è boolean
Con envalid
import { cleanEnv, str, port, bool, url } from 'envalid';
export const env = cleanEnv(process.env, {
NODE_ENV: str({ choices: ['development', 'production', 'test'] }),
PORT: port({ default: 3000 }),
DATABASE_URL: url(),
API_KEY: str(),
DEBUG: bool({ default: false }),
});
// Errore chiaro se manca una variabile richiesta
// "Missing environment variables: DATABASE_URL, API_KEY"
Ambienti Multipli
File per Ambiente
# Struttura file
.env # Default/shared values
.env.local # Override locali (gitignore)
.env.development # Solo sviluppo
.env.production # Solo produzione
.env.test # Solo test
# Caricamento condizionale
const envFile = process.env.NODE_ENV === 'production'
? '.env.production'
: '.env.development';
require('dotenv').config({ path: envFile });
Next.js Built-in Support
# Next.js carica automaticamente in ordine:
.env.local # Sempre ignorato da git
.env.[NODE_ENV] # Per ambiente specifico
.env # Default
# Prefisso NEXT_PUBLIC_ per esporre al client
NEXT_PUBLIC_API_URL=https://api.example.com
DATABASE_URL=postgresql://... # Solo server
// Nel codice client
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
Sicurezza
Cosa NON Fare
# MAI committare secrets nel repo!
# .gitignore DEVE contenere:
.env
.env.local
.env*.local
*.pem
*.key
# MAI hardcodare secrets
const apiKey = 'sk_live_abc123'; // MALE!
# MAI loggare secrets
console.log(process.env); // Espone tutto!
console.log('API Key:', process.env.API_KEY); // MALE!
Best Practices
# Crea .env.example come template
# .env.example (committato nel repo)
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
API_KEY=your-api-key-here
PORT=3000
# Usa secrets manager in produzione
# - AWS Secrets Manager
# - HashiCorp Vault
# - Doppler
# - 1Password Secrets Automation
# Rotazione periodica delle chiavi
# - Cambia API keys ogni 90 giorni
# - Usa short-lived tokens dove possibile
CI/CD
GitHub Actions
# .github/workflows/deploy.yml
name: Deploy
on: push
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build with env
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
API_KEY: ${{ secrets.API_KEY }}
NODE_ENV: production
run: |
npm ci
npm run build
# Aggiungi secrets in:
# Repository → Settings → Secrets and variables → Actions
Vercel/Netlify
# Aggiungi variabili nel dashboard:
# Project → Settings → Environment Variables
# Variabili per ambiente specifico
# Production: API_URL=https://api.prod.com
# Preview: API_URL=https://api.staging.com
# Development: API_URL=http://localhost:4000
Docker
# docker-compose.yml
version: '3.8'
services:
app:
build: .
environment:
- NODE_ENV=production
- PORT=3000
env_file:
- .env.production
# Dockerfile
FROM node:20-alpine
ENV NODE_ENV=production
# ...
# Runtime override
docker run -e API_KEY=xyz -e DEBUG=true myapp
Pattern Comuni
Config Module
// config/index.js
import 'dotenv/config';
import { z } from 'zod';
const envSchema = z.object({
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
PORT: z.coerce.number().default(3000),
DATABASE_URL: z.string(),
REDIS_URL: z.string().optional(),
LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
});
const parsed = envSchema.safeParse(process.env);
if (!parsed.success) {
console.error('Invalid environment variables:', parsed.error.flatten());
process.exit(1);
}
export const config = {
env: parsed.data.NODE_ENV,
port: parsed.data.PORT,
db: {
url: parsed.data.DATABASE_URL,
},
redis: parsed.data.REDIS_URL,
logging: {
level: parsed.data.LOG_LEVEL,
},
isDev: parsed.data.NODE_ENV === 'development',
isProd: parsed.data.NODE_ENV === 'production',
};
// Uso
import { config } from './config';
app.listen(config.port);
Debugging
Il debugging delle variabili d'ambiente può essere complicato. Se una variabile non viene caricata, verifica prima che il file .env sia nella directory corretta. Usa process.cwd() per controllare la working directory. Assicurati che dotenv.config() sia chiamato prima di accedere a process.env. Per debugging, puoi loggare Object.keys(process.env) per vedere tutte le variabili caricate. Ricorda che le variabili definite nel sistema hanno precedenza su quelle del file .env.
Errori Comuni
Gli errori più frequenti includono: spazi intorno al segno uguale che causano valori errati, virgolette non chiuse nelle stringhe multilinea, file .env non salvato in UTF-8, path relativo sbagliato a dotenv.config. Per variabili booleane, ricorda che process.env restituisce sempre stringhe quindi ENABLE_FEATURE=true sarà la stringa true non il booleano. Usa validazione con Zod o envalid per convertire automaticamente i tipi e catturare errori di configurazione all'avvio dell'applicazione invece che a runtime.
Strumenti Correlati
Per lavorare con configurazioni:
- JSON Formatter - Formatta config JSON
- Base64 Encoder - Codifica secrets
- Hash Generator - Genera token sicuri
Conclusione
Le variabili d'ambiente sono fondamentali per:
- Separare configurazione da codice
- Gestire secrets in modo sicuro
- Supportare ambienti multipli
- Facilitare deployment CI/CD
Ricorda: mai committare .env con secrets reali! Usa git hooks con husky per prevenire commit accidentali di file .env. Configura pattern matching nel .gitignore per escludere tutte le varianti del file env.
Per altri strumenti utili, esplora i nostri tool online gratuiti. Per approfondimenti, consulta dotenv documentation.