El problema de la configuración
Un mismo servidor se ejecuta en varios entornos: tu portátil (development),
un servidor de pruebas (staging) y producción (production). Cada uno necesita
valores distintos: el puerto, la URL de la base de datos, las claves de APIs
externas... Si hardcodeas esos valores en el código, mezclas lo que cambia
(la config) con lo que no (el programa), y acabas con un if (entorno === "prod")
esparcido por todas partes.
La metodología 12-factor
The Twelve-Factor App es un conjunto de buenas prácticas para construir servicios. Su factor III (Config) dice:
Guarda la configuración en el entorno, no en el código.
En concreto:
- La config vive en variables de entorno (
process.env), no en el repositorio. - El mismo build (el mismo código y artefacto) se despliega en todos los entornos; lo único que cambia es el entorno que lo rodea.
- Una buena prueba: ¿podrías hacer open source del repo ahora mismo sin filtrar ninguna credencial? Si la respuesta es no, tienes secretos en el código.
// En Node, las variables de entorno llegan en process.env (siempre strings).
const PORT = process.env.PORT; // "3000"
const NODE_ENV = process.env.NODE_ENV; // "production"
Centralizar y poner valores por defecto
En lugar de leer process.env por todo el código, centraliza la lectura en un
único módulo de config que valida y aplica valores por defecto. Así el resto
de la aplicación recibe un objeto limpio y tipado:
function cargarConfig(env) {
return {
port: Number(env.PORT) || 3000,
entorno: env.NODE_ENV || "development",
};
}
const config = cargarConfig(process.env);
En estos ejercicios inyectamos el entorno como un objeto
env(en vez de leerprocess.envdirectamente). Esto hace la config testeable: puedes probar la misma función con{}, con{ PORT: "8080" }, etc.
Separar config por entorno
El valor de NODE_ENV decide el comportamiento: en development quieres logs
detallados y mensajes de error completos; en production, logs compactos y errores
genéricos (para no filtrar detalles internos). El código es el mismo; solo
cambian los valores que recibe.
Gestión de secretos
Las contraseñas, tokens y claves son config especialmente sensible:
- Nunca se commitean. En local se usa un archivo
.env(ignorado por git, cargado con una librería comodotenv); en producción los inyecta la plataforma (Docker, Kubernetes Secrets, AWS Secrets Manager, Vault...). - El repo incluye un
.env.examplecon las claves sin valores, como documentación. - La aplicación debe fallar al arrancar (fail fast) si falta un secreto obligatorio, en vez de romperse a mitad de una petición.
Ejemplos
Config centralizada con defaults y validación de secretos
function cargarConfig(env) {
if (env.NODE_ENV === "production" && !env.DATABASE_URL) {
throw new Error("Falta DATABASE_URL en producción");
}
return {
port: Number(env.PORT) || 3000,
entorno: env.NODE_ENV || "development",
dbUrl: env.DATABASE_URL || "memoria://local",
};
}
console.log(cargarConfig({ PORT: "8080" }));
console.log(cargarConfig({}));