typescript · nestjs · supabase · finance-app-development · drizzleorm
Desarrollando un módulo de configuración a medida para aplicaciones NestJS
¿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
appydb(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
.envespecí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!
🔗 Código: https://github.com/RubenOAlvarado/finance-api/tree/v0.2.0