En esta página

Configuración y gestión de entornos

12 min lectura TextoCap. 5 — Producción

La importancia de una buena gestión de configuración

En una aplicación NestJS, la configuración incluye todo aquello que varía entre entornos: credenciales de base de datos, secrets de JWT, URLs de servicios externos, puertos, timeouts, límites de tasa, etc. Gestionar esto correctamente es fundamental para:

  1. Seguridad: Los secrets no deben estar en el código fuente
  2. Flexibilidad: La misma imagen Docker funciona en development, staging y production
  3. Fiabilidad: Si falta una variable crítica, la aplicación debe fallar al arrancar, no durante la ejecución

Instalación

npm install @nestjs/config @hapi/joi
# O si prefieres la validación con Zod:
npm install @nestjs/config zod

El módulo ConfigModule básico

En su forma más simple, ConfigModule.forRoot() carga el archivo .env y hace las variables disponibles en process.env:

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true, // no necesitas importarlo en cada módulo
    }),
  ],
})
export class AppModule {}

Archivos .env por entorno

Puedes tener múltiples archivos .env para diferentes entornos:

.env                 # valores por defecto o development
.env.development     # solo development (sobreescribe .env)
.env.production      # solo producción
.env.test            # solo testing
.env.local           # local del desarrollador (no commitear)

En ConfigModule.forRoot, especifica el orden de carga:

ConfigModule.forRoot({
  envFilePath: [
    `.env.${process.env['NODE_ENV'] ?? 'development'}`,
    '.env',
  ],
})

Los archivos se cargan en orden y el primero tiene prioridad.

Usando ConfigService

ConfigService es el servicio inyectable que proporciona acceso tipado a las variables:

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class AppService {
  constructor(private readonly config: ConfigService) {}

  getInfo() {
    return {
      nombre: 'Mi API',
      entorno: this.config.get<string>('NODE_ENV', 'development'),
      puerto: this.config.get<number>('PORT', 3000),
    };
  }
}

Validación con Zod (alternativa moderna a Joi)

Si prefieres Zod para la validación:

// config/env.schema.ts
import { z } from 'zod';

export const EnvSchema = z.object({
  NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
  PORT: z.coerce.number().default(3000),
  DB_HOST: z.string().min(1, 'DB_HOST es requerido'),
  DB_PORT: z.coerce.number().default(5432),
  DB_USER: z.string().min(1),
  DB_PASS: z.string().min(1),
  DB_NAME: z.string().min(1),
  JWT_SECRET: z.string().min(32, 'JWT_SECRET debe tener al menos 32 caracteres'),
  JWT_EXPIRES_IN: z.string().default('15m'),
  FRONTEND_URL: z.string().url('FRONTEND_URL debe ser una URL válida'),
});

export type Env = z.infer<typeof EnvSchema>;

// En main.ts — validar antes de arrancar
import { EnvSchema } from './config/env.schema';

async function bootstrap() {
  const resultado = EnvSchema.safeParse(process.env);
  if (!resultado.success) {
    console.error('❌ Variables de entorno inválidas:');
    console.error(resultado.error.flatten().fieldErrors);
    process.exit(1);
  }

  const app = await NestFactory.create(AppModule);
  await app.listen(resultado.data.PORT);
}

Configuración de TypeORM usando ConfigService

TypeOrmModule.forRootAsync({
  imports: [ConfigModule],
  inject: [ConfigService],
  useFactory: (config: ConfigService) => {
    const isProduction = config.get('NODE_ENV') === 'production';
    return {
      type: 'postgres',
      host: config.getOrThrow('DB_HOST'),
      port: config.getOrThrow<number>('DB_PORT'),
      username: config.getOrThrow('DB_USER'),
      password: config.getOrThrow('DB_PASS'),
      database: config.getOrThrow('DB_NAME'),
      autoLoadEntities: true,
      synchronize: !isProduction,
      logging: !isProduction,
      ssl: isProduction ? { rejectUnauthorized: false } : false,
      extra: isProduction
        ? {
            max: 20, // máximo de conexiones en el pool
            connectionTimeoutMillis: 5000,
          }
        : {},
    };
  },
})

Variables de entorno en el archivo .env de ejemplo

Crea un archivo .env.example (que SÍ se commitea) para documentar las variables requeridas:

# .env.example — copia a .env y rellena los valores reales

# Aplicación
NODE_ENV=development
PORT=3000
FRONTEND_URL=http://localhost:4200

# Base de datos PostgreSQL
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASS=tu_password_aqui
DB_NAME=mi_api_dev
DB_SSL=false

# JWT — genera con: node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
JWT_SECRET=CAMBIAR_POR_SECRET_REAL_DE_AL_MENOS_64_CARACTERES
JWT_EXPIRES_IN=15m
JWT_REFRESH_SECRET=CAMBIAR_POR_SECRET_DIFERENTE_PARA_REFRESH
JWT_REFRESH_EXPIRES_IN=7d

# Redis (opcional)
REDIS_HOST=localhost
REDIS_PORT=6379

Acceder a la configuración en main.ts

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const config = app.get(ConfigService);

  const puerto = config.getOrThrow<number>('PORT');
  const entorno = config.get('NODE_ENV', 'development');

  // Configurar CORS basado en el entorno
  if (entorno !== 'production') {
    app.enableCors({ origin: true });
  } else {
    app.enableCors({
      origin: config.getOrThrow('FRONTEND_URL'),
      credentials: true,
    });
  }

  await app.listen(puerto);
  console.log(`Aplicación corriendo en el puerto ${puerto} (${entorno})`);
}

Una configuración robusta es la base de una aplicación que funciona correctamente en todos los entornos. En la próxima lección aprenderemos a documentar nuestra API automáticamente con Swagger, generando documentación interactiva directamente desde los decoradores de NestJS.

Valida siempre las variables de entorno al arrancar
Sin validación, un error tipográfico en una variable de entorno puede causar fallos silenciosos en producción (por ejemplo, una URL de base de datos vacía que conecta a localhost). Con Joi, si falta una variable requerida o tiene un formato incorrecto, la aplicación falla ANTES de arrancar con un mensaje claro de qué falta.
Usa getOrThrow() en lugar de get() para variables críticas
El método `config.get()` retorna `undefined` si la variable no existe (a menos que tengas activada la validación). El método `config.getOrThrow()` lanza un error si la variable no está definida. Úsalo en la inicialización de componentes críticos como la conexión a la base de datos para fallar rápido y con un mensaje claro.