En esta página
Módulos y estructura de la aplicación
El sistema de módulos de NestJS
Los módulos son el corazón de la arquitectura de NestJS. Cada aplicación tiene al menos un módulo —el módulo raíz— y la inmensa mayoría de las aplicaciones reales tienen docenas de módulos, uno por cada dominio de funcionalidad de negocio.
Un módulo en NestJS es simplemente una clase decorada con @Module(). Este decorador acepta un objeto de metadatos que describe las relaciones del módulo con el resto de la aplicación.
La anatomía del decorador @Module
@Module({
imports: [], // módulos externos cuyos providers necesitamos
controllers: [], // controladores que pertenecen a este módulo
providers: [], // servicios, repositorios y otros providers del módulo
exports: [], // providers que otros módulos podrán inyectar
})Cada propiedad tiene un propósito específico:
imports: Lista de módulos que este módulo necesita. Cuando importas un módulo, todos sus exports quedan disponibles para ser inyectados en los providers de este módulo. Esta es la forma en que los módulos se comunican entre sí.
controllers: Lista de controladores que manejan las rutas HTTP de este módulo. Los controladores son instanciados por NestJS y sus dependencias son resueltas por el contenedor de IoC.
providers: Lista de providers (servicios, repositorios, factories, etc.) que serán instanciados por el contenedor de IoC de NestJS y que pueden ser inyectados dentro de este módulo.
exports: Subconjunto de providers que este módulo pone a disposición de otros módulos. Si un módulo no exporta un provider, ese provider es privado y solo puede usarse dentro del mismo módulo.
Módulos de funcionalidad (Feature Modules)
La práctica recomendada en NestJS es crear un módulo por cada dominio de funcionalidad de tu aplicación. Si estás construyendo una tienda en línea, tendrás módulos como ProductosModule, UsuariosModule, PedidosModule, PagosModule, etc.
Creando un módulo de funcionalidad con el CLI:
nest generate module productos
nest generate controller productos
nest generate service productosO de forma abreviada:
nest g mo productos
nest g co productos
nest g s productosEl CLI creará automáticamente la carpeta productos/ y actualizará el AppModule para importar el nuevo ProductosModule.
El árbol de módulos
NestJS construye un árbol de módulos en tiempo de arranque. El AppModule es la raíz de ese árbol. Cuando NestJS inicializa la aplicación, recorre el árbol de módulos en profundidad para instanciar todos los providers en el orden correcto, respetando las dependencias.
Este árbol tiene una propiedad importante: el scope. Un provider instanciado en el UsuariosModule solo es accesible dentro de ese módulo, a menos que sea exportado. Esto crea encapsulación real —como si cada módulo fuera un mini-aplicación con su propio contenedor de IoC.
Módulos compartidos
En aplicaciones reales, varios módulos suelen necesitar los mismos services. Por ejemplo, un LoggerService podría ser necesario en UsuariosModule, ProductosModule y PedidosModule. La solución es crear un módulo compartido:
// shared/logger/logger.module.ts
import { Module } from '@nestjs/common';
import { LoggerService } from './logger.service';
@Module({
providers: [LoggerService],
exports: [LoggerService],
})
export class LoggerModule {}Cualquier módulo que importe LoggerModule podrá inyectar LoggerService. Lo importante es que NestJS reutiliza la misma instancia del módulo a lo largo del árbol de módulos —el módulo no se reinstancia en cada importación.
Módulos globales (@Global)
Cuando un provider necesita estar disponible en toda la aplicación sin necesidad de importar el módulo en cada lugar, puedes usar el decorador @Global:
import { Global, Module } from '@nestjs/common';
import { ConfigService } from './config.service';
@Global()
@Module({
providers: [ConfigService],
exports: [ConfigService],
})
export class ConfigModule {}Con @Global, solo necesitas importar ConfigModule una vez —en el AppModule— y ConfigService estará disponible en toda la aplicación. Úsalo con moderación: hacer demasiados módulos globales puede hacer que las dependencias sean difíciles de rastrear.
Módulos dinámicos
Los módulos dinámicos permiten crear módulos configurables que aceptan opciones en tiempo de importación. Este patrón es muy común en librerías de NestJS como @nestjs/config, @nestjs/jwt y @nestjs/typeorm.
La convención establece tres métodos estáticos:
forRoot(options): para configuración global (normalmente enAppModule)forFeature(options): para configuración específica de un módulo de funcionalidadregister(options): para módulos que se usan múltiples veces con distintas configuraciones
// emails/email.module.ts
import { DynamicModule, Module } from '@nestjs/common';
import { EmailService } from './email.service';
export interface EmailConfig {
host: string;
port: number;
user: string;
password: string;
}
@Module({})
export class EmailModule {
static register(config: EmailConfig): DynamicModule {
return {
module: EmailModule,
providers: [
{
provide: 'EMAIL_CONFIG',
useValue: config,
},
EmailService,
],
exports: [EmailService],
};
}
}
// En app.module.ts
@Module({
imports: [
EmailModule.register({
host: 'smtp.gmail.com',
port: 587,
user: process.env['EMAIL_USER'] ?? '',
password: process.env['EMAIL_PASS'] ?? '',
}),
],
})
export class AppModule {}Lazy loading de módulos
En aplicaciones muy grandes o en contextos serverless donde el tiempo de arranque es crítico, puedes cargar módulos de forma diferida usando el LazyModuleLoader:
import { Injectable } from '@nestjs/common';
import { LazyModuleLoader } from '@nestjs/core';
@Injectable()
export class AppService {
constructor(private readonly lazyModuleLoader: LazyModuleLoader) {}
async getReporteComplejo() {
const { ReportesModule } = await import('./reportes/reportes.module');
const moduleRef = await this.lazyModuleLoader.load(() => ReportesModule);
const { ReportesService } = await import('./reportes/reportes.service');
const reportesService = moduleRef.get(ReportesService);
return reportesService.generarReporte();
}
}Re-exportando módulos
Un módulo puede re-exportar módulos que importa, actuando como un módulo de "re-exportación" o "facade". Esto es útil para crear módulos de utilidades que agregan varios módulos pequeños:
@Module({
imports: [LoggerModule, CacheModule, DatabaseModule],
exports: [LoggerModule, CacheModule, DatabaseModule], // re-exporta los tres
})
export class CoreModule {}Ahora cualquier módulo que importe CoreModule obtendrá acceso a los exports de los tres módulos internos.
Estructura de carpetas recomendada
Para un proyecto NestJS mediano o grande, esta estructura de carpetas escala bien:
src/
├── common/ # Utilities, guards, interceptors compartidos
│ ├── decorators/
│ ├── filters/
│ ├── guards/
│ └── interceptors/
├── config/ # Configuración de la aplicación
├── database/ # Módulo de base de datos
├── usuarios/ # Feature module: usuarios
│ ├── dto/
│ ├── entities/
│ ├── usuarios.controller.ts
│ ├── usuarios.module.ts
│ └── usuarios.service.ts
├── productos/ # Feature module: productos
│ ├── dto/
│ ├── entities/
│ ├── productos.controller.ts
│ ├── productos.module.ts
│ └── productos.service.ts
├── app.module.ts
└── main.tsEsta estructura garantiza que cada dominio de negocio está perfectamente encapsulado y puede evolucionar de forma independiente. En la próxima lección exploraremos los controladores —la capa que expone tu API al mundo exterior.
Inicia sesión para guardar tu progreso