Inicio / Artículos

Valida las variables de entorno de Node.js con Zod y TypeScript

Deja de leer process.env en todo el código de Node.js. Un archivo env.ts validado con Zod detecta los secretos faltantes al iniciar y proporciona a la aplicación una configuración de TypeScript tipada.

Editor: REACTAPP.TOP3 min de lectura663 palabras

Fuentes: https://medium.com/@vetriselvan_11/validating-environment-variables-in-node-js-using-zod-typescript-guide-16f4b6bacaf4

Imagínese una implementación el viernes por la tarde. La compilación tiene éxito, el servidor arranca y todos se van a casa. Una hora después, los usuarios informan que los inicios de sesión fallan. Resulta que nadie estableció el valor de JWT_SECRET en el entorno de producción. La aplicación funcionó bien al principio y solo falló cuando la primera solicitud accedió al código que necesitaba ese valor faltante.

Consultar constantemente process.env en todo el código hace que este tipo de incidentes sean probables. A continuación se muestra cómo reemplazar esa práctica por un módulo de configuración validado y con tipos definidos, utilizando Zod y TypeScript.

El enfoque habitual

La mayoría del código de Node.js obtiene la configuración justo donde se necesita: un puerto en un archivo, una cadena de conexión a la base de datos en otro.

// Some random controller file
const port = process.env.PORT || 3000;
mongoose.connect(process.env.MONGO_URI);

Es práctico, pero tiene tres problemas reales.

  1. Falta de seguridad en los tipos. En TypeScript, process.env.PORT es de tipo string | undefined. Si se necesita un número, cada lugar donde se utilice debe parsearlo manualmente.
  2. Fallas silenciosas. Cuando MONGO_URI no está definido, el proceso no se cae al iniciar. Sí se cae cuando finalmente se ejecuta esa línea, posiblemente horas después de un despliegue que parecía correcto.
  3. Falta de una única fuente de verdad. Un recién llegado no cuenta con una lista de las variables que necesita la aplicación. Su única opción es buscar en todo el repositorio la referencia a process.env.

Un único archivo de control

La solución es dejar de modificar process.env en la lógica de negocio. Un único módulo, llamémoslo env.ts, lee el entorno una sola vez al iniciar, lo valida y exporta un objeto de tipo estricto. Todo lo demás importa ese objeto.

Zod, una biblioteca de validación de esquemas orientada a TypeScript, se encarga de la validación.

Instálala

npm install zod

Escribe el esquema

Coloca el módulo en src/config/env.ts y describe con exactitud cómo debe ser el entorno.

import dotenv from 'dotenv';
import { z } from 'zod';

// Load variables from .env file (if in development)
dotenv.config();

// 1. Define the schema
const envSchema = z.object({
  PORT: z.string().transform(Number).default('3000'),
  NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
  MONGO_URI: z.string(),
  JWT_SECRET: z.string(),
  JWT_EXPIRES_IN: z.string().default('1d'),
});

// 2. Parse the environment against the schema
const _env = envSchema.safeParse(process.env);

// 3. Fail fast if it's invalid
if (!_env.success) {
  console.error('Invalid environment variables:', _env.error.format());
  process.exit(1); // Crash the app immediately!
}

// 4. Export the typed, validated object
export const env = _env.data;

Qué hace el módulo

  • Declara las expectativas. MONGO_URI es una cadena obligatoria. PORT se proporciona como cadena, y .transform(Number) la convierte, con un valor por defecto de 3000. NODE_ENV debe ser uno de los tres valores permitidos.
  • Genera un error de inmediato. safeParse se ejecuta en el momento en que se importa el archivo, por lo que hay que importarlo en la parte superior de su punto de entrada, ya sea app.ts o server.ts. Si falta o está mal formada alguna variable obligatoria, el resultado indica un fallo, la salida de error indica con precisión qué salió mal y process.exit(1) detiene el proceso antes de que sirva tráfico.
  • Exporta un objeto tipado. En caso de éxito, los datos analizados incluyen los tipos inferidos por Zod, de modo que env.PORT ya es un número.

A partir de entonces, cualquier archivo puede importar env y utilizarlo.

Qué obtienes

  • Autocompletado. Al escribir env. en tu editor, se muestran todas las variables disponibles.
  • Sin soluciones alternativas dispersas. Los patrones || "default_value" desaparecen de los controladores, ya que los valores por defecto se encuentran en el esquema.
  • Documentación integrada. El esquema es una lista precisa de lo que necesita la aplicación para funcionar, lo que también responde a las dudas de los recién llegados sobre qué debe incluirse en .env.

Un valor secreto faltante ahora impide el despliegue en cuestión de segundos, al iniciar la aplicación, con un mensaje claro, en lugar de que un usuario molesto lo descubra una hora después.

Leer más