typescript · nestjs · supabase · finance-app-development · drizzleorm

Desarrollando un módulo de configuración a medida para aplicaciones NestJS

4 min de lectura

¿Alguna vez pasó tener más variables de entorno que funciones reales? DATABASE_URL, SUPABASE_URL, JWT_SECRET, un par de flags para local y producción, y tal vez algunas variables “temporales” que se prometen limpiar después.

Si se lee process.env directamente en todos lados, el código se vuelve frágil rápido:

  • Un simple typo rompe la conexión silenciosamente.

  • Una variable faltante hace que la app crashee en runtime.

  • Se termina depurando configuración en lugar de avanzar con el desarrollo.

¿Y si existiera una forma mejor, más limpia y más profesional de manejar este caos? En este artículo vamos a construir un módulo de configuración pequeño, type-safe y validado, que servirá de base para el resto de esta serie.

¿Por qué un módulo de configuración personalizado?

Nest ya ofrece @nestjs/config, y es genial. El problema es que la mayoría de los tutoriales se quedan en “instalarlo y ya”.

Para una API de nivel producción, se quiere un poco más:

  • Un solo lugar para cargar y validar las variables de entorno

  • Namespaces claros como app y db (por ahora)

  • Inferencia de tipos para que acceder a la configuración sea seguro y fácil de descubrir

  • Validaciones fail fast (antes de que la app arranque)

Esto es especialmente importante en nuestra Personal Finance API, porque los próximos pasos dependen de una configuración estable:

  • Drizzle necesita un connection string de Postgres válido

  • Los entornos local y de producción deben comportarse de forma predecible

Creación del módulo

Vamos a crear un módulo global que actúe como única fuente de verdad para la configuración.

Generar un módulo de configuración (se puede elegir la ruta que se prefiera):

nest g mo config

Ahora hay que configurar el módulo usando el ConfigModule de Nest, pero manteniéndolo envuelto detrás de un módulo propio.

Esta es la base:

// config/config.module.ts
import { Global, Module } from '@nestjs/common';
import { ConfigModule as NestConfigModule } from '@nestjs/config';

@Global()
@Module({
  imports: [
    NestConfigModule.forRoot({
      isGlobal: true,
      cache: true,
      envFilePath: [`.env.${process.env.NODE_ENV}`, '.env'],
    }),
  ],
  exports: [NestConfigModule],
})
export class ConfigModule {}

Esto ya nos da:

  • Configuración global (no hace falta importarla en todos lados)

  • Carga de .env específica por entorno

  • Caching para mejorar el rendimiento

Definiendo namespaces de configuración (app, db)

En lugar de dispersar nombres de variables por todo el código, vamos a crear “namespaces” de configuración. Esto mantiene todo organizado y facilita seguir los próximos artículos.

Ejemplo: configuración de app. Se puede hacer lo mismo para db.

// config/configurations/app.config.ts
import { registerAs } from '@nestjs/config';

export default registerAs(
  'app',
  (): AppConfig => ({
    port: parseInt(process.env.PORT || '3000', 10),
    nodeEnv: (process.env.NODE_ENV || 'development') as AppConfig['nodeEnv'],
  }),
);

// config/types/app-config.types.ts
export type AppConfig = {
  port: number;
  nodeEnv: 'development' | 'production' | 'test' | 'staging';
};

Para esta serie, lo importante es que para cuando conectemos Drizzle, podamos leer algo como:

  • db.connectionString

Validando las variables de entorno con Joi

El type safety está bien, pero es la validación la que evita que una configuración incorrecta llegue a producción. Vamos a usar Joi para validar las env vars antes de que la app arranque.

Instalar Joi:

pnpm i joi

Crear un schema de validación:

// config/validations/env.validation.ts
import * as Joi from 'joi';

export const envValidationSchema = Joi.object({
  PORT: Joi.number().default(3000),
  NODE_ENV: Joi.string()
    .valid('development', 'production', 'test', 'staging')
    .default('development'),
});

Ahora hay que conectar todo:

// config/config.module.ts
import { Global, Module } from '@nestjs/common';
import { ConfigModule as NestConfigModule } from '@nestjs/config';

import appConfig from './configurations/app.config';
import { envValidationSchema } from './validations/env.validation';

@Global()
@Module({
  imports: [
    NestConfigModule.forRoot({
      isGlobal: true,
      cache: true,
      envFilePath: [`.env.${process.env.NODE_ENV}`, '.env'],
      load: [appConfig],
      validationSchema: envValidationSchema,
    }),
  ],
  exports: [NestConfigModule],
})
export class ConfigModule {}

Probándolo rápidamente

Crear un archivo .env con las variables definidas y levantar la app. Si falta alguna variable o es inválida, Nest va a fallar rápido e indicar exactamente qué está mal. Ese es todo el punto.

Si aparece Nest application successfully started en la consola, está listo.

Cierre

Llegados a este punto, ya se tiene una capa de configuración que es:

  • Centralizada (un solo módulo)

  • Type-safe (objetos de configuración estructurados)

  • Validada (schema de Joi)

  • Lista para los próximos pasos (integración de Drizzle + Supabase)

En el próximo artículo vamos a usar este módulo para inicializar Drizzle con el connection string de Postgres de Supabase y empezar a definir el schema. Pero para estar listos, vamos a necesitar la configuración de la base de datos — así que esa es la tarea. ¡Nos vemos en el próximo!

💡
Próximo artículo: Conectar Supabase Postgres a NestJS usando Drizzle y nuestro módulo de configuración.

🔗 Código: https://github.com/RubenOAlvarado/finance-api/tree/v0.2.0