¿Por qué usar variables de entorno?
Las variables de entorno permiten configurar tu aplicación sin cambiar el código. Esto es útil para:
- Separar configuración de lógica: el código no “sabe” si está en desarrollo o producción; lo decide la configuración.
- Evitar exponer secretos: contraseñas, tokens y URLs privadas no deben quedar en el repositorio.
- Facilitar despliegues: cada entorno (local, staging, producción) puede tener valores distintos para la misma app.
En Node.js, las variables de entorno se leen desde process.env, un objeto que contiene pares clave/valor (siempre como strings o undefined).
Leer variables con process.env
Ejemplo directo:
const port = process.env.PORT; // string o undefinedDetalles importantes:
- Si una variable no existe, su valor será
undefined. - Todo llega como texto. Si necesitas un número o booleano, conviértelo explícitamente.
Ejemplo con conversiones seguras:
- Escuche el audio con la pantalla apagada.
- Obtenga un certificado al finalizar.
- ¡Más de 5000 cursos para que explores!
Descargar la aplicación
const PORT = Number(process.env.PORT || 3000); // 3000 si no existe PORTUsar dotenv en desarrollo (sin complicarlo)
En producción, lo habitual es que la plataforma (Docker, systemd, el proveedor cloud, etc.) inyecte las variables de entorno. En desarrollo local, es común usar un archivo .env para definirlas, y dotenv para cargarlas en process.env.
Paso a paso: crear un .env local
Crea un archivo
.enven la raíz del proyecto (solo para tu máquina):PORT=3000 DATABASE_URL=postgres://user:pass@localhost:5432/mydb NODE_ENV=developmentCarga
dotenval inicio del arranque de la app (antes de leerprocess.env):// index.js (o el archivo de entrada) if (process.env.NODE_ENV !== 'production') { require('dotenv').config(); }
Con esto, en desarrollo dotenv leerá el archivo .env y poblará process.env. En producción no lo necesitas (y es preferible no depender de archivos locales).
Crear un módulo de configuración con validación (config/)
Leer variables “sueltas” por todo el código suele causar errores difíciles de rastrear (por ejemplo, process.env.DATABASE_URL faltante en un punto específico). Una práctica simple es centralizar la configuración en un módulo que:
- Lea variables una sola vez.
- Valide las requeridas (por ejemplo,
PORT,DATABASE_URL). - Convierta tipos (número, booleano).
- Exponga un objeto
configconsistente para el resto de la app.
Estructura sugerida
config/
index.js
env.js
validate.jsconfig/env.js: cargar dotenv solo cuando corresponde
// config/env.js
function loadEnv() {
const nodeEnv = process.env.NODE_ENV || 'development';
if (nodeEnv !== 'production') {
require('dotenv').config();
}
return nodeEnv;
}
module.exports = { loadEnv };config/validate.js: helpers de validación y parsing
// config/validate.js
function required(name, value) {
if (value === undefined || value === '') {
throw new Error(`Missing required env var: ${name}`);
}
return value;
}
function asNumber(name, value) {
const n = Number(value);
if (!Number.isFinite(n)) {
throw new Error(`Env var ${name} must be a number`);
}
return n;
}
module.exports = { required, asNumber };config/index.js: construir el objeto de configuración
// config/index.js
const { loadEnv } = require('./env');
const { required, asNumber } = require('./validate');
const NODE_ENV = loadEnv();
const config = {
env: NODE_ENV,
port: asNumber('PORT', required('PORT', process.env.PORT)),
databaseUrl: required('DATABASE_URL', process.env.DATABASE_URL),
};
module.exports = { config };Uso desde el resto de la app:
const { config } = require('./config');
console.log(config.env);
console.log(config.port);
// usar config.databaseUrl para inicializar tu capa de datosVentajas: si falta una variable, el error ocurre al iniciar la app, con un mensaje claro, en lugar de fallar más tarde en una ruta o servicio.
Separar configuración por entorno (development/production) sin complejidad
Una separación simple consiste en:
- Usar
NODE_ENVpara decidir comportamiento. - Mantener las mismas claves en todos los entornos (por ejemplo, siempre
DATABASE_URL), cambiando solo los valores. - Evitar múltiples archivos de configuración difíciles de sincronizar.
Ejemplo: activar logs más verbosos solo en development
// config/index.js (extensión simple)
const isProd = config.env === 'production';
config.logging = {
level: isProd ? 'info' : 'debug',
};Ejemplo: permitir un valor por defecto solo en desarrollo
En producción, es mejor exigir variables críticas. En desarrollo, a veces conviene un fallback controlado:
// config/index.js (variación)
const isProd = NODE_ENV === 'production';
const portValue = process.env.PORT;
const port = portValue ? asNumber('PORT', portValue) : (isProd ? undefined : 3000);
const config = {
env: NODE_ENV,
port: isProd ? asNumber('PORT', required('PORT', portValue)) : port,
databaseUrl: required('DATABASE_URL', process.env.DATABASE_URL),
};Así, en desarrollo puedes arrancar sin definir PORT, pero en producción se exige explícitamente.
Buenas prácticas: secretos fuera del repositorio
No versionar .env
El archivo .env suele contener secretos. Debe estar en .gitignore:
# .gitignore
.envSi ya se versionó por error, no basta con borrarlo: hay que rotar credenciales y limpiar el historial (según el caso), porque el secreto pudo quedar expuesto.
Proveer un .env.example
Para que otras personas (o tú en otra máquina) sepan qué variables necesita el proyecto, crea un archivo .env.example con las claves requeridas, pero sin secretos reales:
# .env.example
NODE_ENV=development
PORT=3000
DATABASE_URL=postgres://USER:PASSWORD@HOST:5432/DBNAMEEste archivo sí se versiona. Sirve como “contrato” de configuración.
Pautas rápidas para manejar secretos
- No guardes tokens, passwords o claves privadas en el código ni en archivos versionados.
- Usa variables de entorno inyectadas por el sistema en producción.
- Valida al inicio que existan las variables críticas (por ejemplo,
DATABASE_URL). - Evita imprimir secretos en logs (por ejemplo, no hagas
console.log(process.env)en producción).
Checklist práctico de implementación
- Crear
.envlocal conPORT,DATABASE_URL,NODE_ENV. - Agregar
.enva.gitignore. - Crear
.env.examplecon las claves necesarias. - Implementar
config/para cargar (dotenvsolo fuera de producción), validar y exponerconfig. - Reemplazar lecturas directas de
process.enven el resto del proyecto porconfig.