En la parte 2 dejamos la contraseña de la base de datos escrita en app.module.ts. Si subes ese código a GitHub, cualquiera la ve. Además, cambiar de tu máquina al servidor te obliga a editar el código.
La solución son las variables de entorno: guardas la configuración fuera del código y cada entorno (tu laptop, pruebas, producción) usa sus propios valores. Nest resuelve esto con ConfigModule.
Paso 1: Instala las dependencias
npm i @nestjs/config joi
@nestjs/config lee el archivo .env y te da un servicio para consultar los valores. joi valida que no falte ninguno al arrancar.
Paso 2: Crea el archivo .env
En la raíz del proyecto (junto a package.json) crea .env:
PORT=3000 DB_HOST=localhost DB_PORT=5432 DB_USER=postgres DB_PASSWORD=postgres DB_NAME=tareas_db DB_SYNC=true
DB_SYNC reemplaza el synchronize: true que dejamos fijo en la parte 2. Así lo activas en desarrollo y lo apagas en producción sin tocar código.
Paso 3: Protege el archivo con .gitignore
Abre .gitignore y comprueba que incluya esta línea. Si falta, agrégala:
.env
Para que otras personas sepan qué variables necesita tu proyecto, crea también .env.example con los mismos nombres y valores de ejemplo, sin datos reales. Ese archivo sí se sube al repositorio.
Paso 4: Carga ConfigModule con validación
Abre src/app.module.ts:
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import * as Joi from 'joi';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TareasModule } from './tareas/tareas.module';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
validationSchema: Joi.object({
PORT: Joi.number().default(3000),
DB_HOST: Joi.string().required(),
DB_PORT: Joi.number().default(5432),
DB_USER: Joi.string().required(),
DB_PASSWORD: Joi.string().required(),
DB_NAME: Joi.string().required(),
DB_SYNC: Joi.string().valid('true', 'false').default('false'),
}),
}),
TypeOrmModule.forRootAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
type: 'postgres',
host: config.get<string>('DB_HOST'),
port: config.get<number>('DB_PORT'),
username: config.get<string>('DB_USER'),
password: config.get<string>('DB_PASSWORD'),
database: config.get<string>('DB_NAME'),
autoLoadEntities: true,
synchronize: config.get<string>('DB_SYNC') === 'true',
}),
}),
TareasModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Qué hace cada pieza:
isGlobal: truedejaConfigServicedisponible en toda la app. Sin esto tendrías que importarConfigModuleen cada módulo.validationSchemarevisa las variables al arrancar. Si faltaDB_HOST, la app no levanta y te dice cuál falta, en lugar de fallar después con un error confuso.forRootAsyncespera a queConfigServiceesté listo y con eso arma la conexión. ConforRootnormal no puedes leer la configuración, porque todavía no existe.DB_SYNCllega como texto, así que lo comparo con'true'.
Paso 5: Lee el puerto en main.ts
Abre src/main.ts y cambia el puerto fijo por el valor del .env:
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
const config = app.get(ConfigService);
await app.listen(config.get<number>('PORT') ?? 3000);
}
bootstrap();
app.get(ConfigService) saca el servicio del contenedor de Nest cuando estás fuera de una clase inyectable, como aquí.
Paso 6: Prueba la validación
Arranca con npm run start:dev y comprueba que todo sigue funcionando. Luego borra una línea del .env, por ejemplo DB_NAME, y vuelve a arrancar. Nest se detiene con un mensaje parecido a este:
Error: Config validation error: "DB_NAME" is required
Eso es lo que buscas: fallar rápido y con un mensaje claro. Vuelve a poner la variable antes de seguir.
Cómo usar ConfigService en tus propias clases
Cuando necesites un valor en un servicio, lo inyectas por constructor:
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class EjemploService {
constructor(private readonly config: ConfigService) {}
puerto(): number | undefined {
return this.config.get<number>('PORT');
}
}
Errores comunes
- Las variables llegan como
undefined: el.envno está en la raíz del proyecto, o lo ejecutas desde otra carpeta. Nest can't resolve dependencies ... ConfigService: olvidasteisGlobal: trueo no importasteConfigModuleen ese módulo.- El puerto llega como texto: sin
validationSchematodo valor deprocess.enves texto. Con el esquema de Joi,PORTyDB_PORTse convierten en número.
Lo que hicimos
- Instalamos
@nestjs/configyjoi. - Movimos la configuración de la base a un archivo
.envfuera del repositorio. - Validamos las variables al arrancar.
- Configuramos TypeORM con
forRootAsync. - Leímos el puerto desde la configuración.
Lo que sigue
La API ya está ordenada, pero la validación de datos que hicimos en la parte 1 es básica. En la parte 4 vemos DTOs más completos: enumeraciones, fechas, listas, paginación y mensajes de error propios.
⬅️ Anterior: Cómo conectar NestJS con PostgreSQL usando TypeORM ➡️ Siguiente: Validar datos en NestJS con DTO y class-validator
