Configuración con variables de entorno en aplicaciones Node.js

Capítulo 8

Tiempo estimado de lectura: 5 minutos

+ Ejercicio

¿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 undefined

Detalles 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:

Continúa en nuestra aplicación.
  • Escuche el audio con la pantalla apagada.
  • Obtenga un certificado al finalizar.
  • ¡Más de 5000 cursos para que explores!
O continúa leyendo más abajo...
Download App

Descargar la aplicación

const PORT = Number(process.env.PORT || 3000); // 3000 si no existe PORT

Usar 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

  1. Crea un archivo .env en la raíz del proyecto (solo para tu máquina):

    PORT=3000
    DATABASE_URL=postgres://user:pass@localhost:5432/mydb
    NODE_ENV=development
  2. Carga dotenv al inicio del arranque de la app (antes de leer process.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 config consistente para el resto de la app.

Estructura sugerida

config/
  index.js
  env.js
  validate.js

config/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 datos

Ventajas: 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_ENV para 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
.env

Si 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/DBNAME

Este 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 .env local con PORT, DATABASE_URL, NODE_ENV.
  • Agregar .env a .gitignore.
  • Crear .env.example con las claves necesarias.
  • Implementar config/ para cargar (dotenv solo fuera de producción), validar y exponer config.
  • Reemplazar lecturas directas de process.env en el resto del proyecto por config.

Ahora responde el ejercicio sobre el contenido:

¿Cuál es la forma más adecuada de manejar la configuración y variables críticas (como PORT y DATABASE_URL) en una app Node.js para evitar fallos tardíos y no exponer secretos?

¡Tienes razón! Felicitaciones, ahora pasa a la página siguiente.

¡Tú error! Inténtalo de nuevo.

Centralizar la configuración permite validar variables requeridas y convertir tipos al arrancar, fallando temprano con errores claros. Además, dotenv se usa típicamente solo en desarrollo y el archivo .env no debe versionarse.

Siguiente capítulo

Logging básico para APIs Node.js

Arrow Right Icon
Portada de libro electrónico gratuitaNode.js para principiantes: crea un backend simple con Express
67%

Node.js para principiantes: crea un backend simple con Express

Nuevo curso

12 páginas

Descarga la aplicación para obtener una certificación gratuita y escuchar cursos en segundo plano, incluso con la pantalla apagada.