Patrones de Desarrollo NestJS
Patrones de NestJS de grado de producción para backends TypeScript modulares.
Cuándo Activar
- Construyendo APIs o servicios NestJS
- Estructurando módulos, controladores y proveedores
- Añadiendo validación de DTOs, guards, interceptores o filtros de excepción
- Configurando ajustes sensibles al entorno e integraciones de bases de datos
- Probando unidades NestJS o endpoints HTTP
Estructura del Proyecto
src/
├── app.module.ts
├── main.ts
├── common/
│ ├── filters/
│ ├── guards/
│ ├── interceptors/
│ └── pipes/
├── config/
│ ├── configuration.ts
│ └── validation.ts
├── modules/
│ ├── auth/
│ │ ├── auth.controller.ts
│ │ ├── auth.module.ts
│ │ ├── auth.service.ts
│ │ ├── dto/
│ │ ├── guards/
│ │ └── strategies/
│ └── users/
│ ├── dto/
│ ├── entities/
│ ├── users.controller.ts
│ ├── users.module.ts
│ └── users.service.ts
└── prisma/ o database/
- Mantén el código de dominio dentro de los módulos de características.
- Coloca filtros, decoradores, guards e interceptores transversales en
common/. - Mantén los DTOs cerca del módulo que los posee.
Bootstrap y Validación Global
async function bootstrap() {
const app = await NestFactory.create(AppModule, { bufferLogs: true });
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
transformOptions: { enableImplicitConversion: true },
}),
);
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
- Siempre habilita
whitelistyforbidNonWhitelisteden APIs públicas. - Prefiere un pipe de validación global único en lugar de repetir la configuración de validación por ruta.
Módulos, Controladores y Proveedores
@Module({
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get(':id')
getById(@Param('id', ParseUUIDPipe) id: string) {
return this.usersService.getById(id);
}
@Post()
create(@Body() dto: CreateUserDto) {
return this.usersService.create(dto);
}
}
@Injectable()
export class UsersService {
constructor(private readonly usersRepo: UsersRepository) {}
async create(dto: CreateUserDto) {
return this.usersRepo.create(dto);
}
}
- Los controladores deben ser delgados: analizar entrada HTTP, llamar a un proveedor y devolver DTOs de respuesta.
- Coloca la lógica de negocio en servicios inyectables, no en controladores.
- Exporta solo los proveedores que otros módulos realmente necesiten.
DTOs y Validación
export class CreateUserDto {
@IsEmail()
email!: string;
@IsString()
@Length(2, 80)
name!: string;
@IsOptional()
@IsEnum(UserRole)
role?: UserRole;
}
- Valida cada DTO de solicitud con
class-validator. - Usa DTOs de respuesta dedicados o serializadores en lugar de devolver entidades ORM directamente.
- Evita filtrar campos internos como hashes de contraseñas, tokens o columnas de auditoría.
Autenticación, Guards y Contexto de Solicitud
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@Get('admin/report')
getAdminReport(@Req() req: AuthenticatedRequest) {
return this.reportService.getForUser(req.user.id);
}
- Mantén las estrategias de autenticación y guards locales al módulo a menos que sean realmente compartidos.
- Codifica reglas de acceso gruesas en guards, luego realiza autorización específica de recursos en servicios.
- Prefiere tipos de solicitud explícitos para objetos de solicitud autenticados.
Filtros de Excepción y Forma del Error
@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const response = host.switchToHttp().getResponse<Response>();
const request = host.switchToHttp().getRequest<Request>();
if (exception instanceof HttpException) {
return response.status(exception.getStatus()).json({
path: request.url,
error: exception.getResponse(),
});
}
return response.status(500).json({
path: request.url,
error: 'Error interno del servidor',
});
}
}
- Mantén una envoltura de error consistente en toda la API.
- Lanza excepciones del framework para errores esperados del cliente; registra y envuelve fallos inesperados de forma centralizada.
Configuración y Validación del Entorno
ConfigModule.forRoot({
isGlobal: true,
load: [configuration],
validate: validateEnv,
});
- Valida el entorno al arranque, no perezosamente en la primera solicitud.
- Mantén el acceso a la configuración detrás de helpers tipados o servicios de configuración.
- Divide las preocupaciones de desarrollo/staging/producción en fábricas de configuración en lugar de bifurcar en todo el código de características.
Persistencia y Transacciones
- Mantén el código de repositorio/ORM detrás de proveedores que hablen el lenguaje de dominio.
- Para Prisma o TypeORM, aísla los flujos de trabajo transaccionales en servicios que poseen la unidad de trabajo.
- No permitas que los controladores coordinen escrituras de múltiples pasos directamente.
Pruebas
describe('UsersController', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [UsersModule],
}).compile();
app = moduleRef.createNestApplication();
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
await app.init();
});
});
- Prueba unidades de proveedores de forma aislada con dependencias simuladas.
- Agrega pruebas a nivel de solicitud para guards, pipes de validación y filtros de excepción.
- Reutiliza los mismos pipes/filtros globales en las pruebas que usas en producción.
Valores Predeterminados de Producción
- Habilita el registro estructurado y los IDs de correlación de solicitudes.
- Termina en entorno/configuración inválidos en lugar de arrancar parcialmente.
- Prefiere la inicialización asíncrona de proveedores para clientes de base de datos/caché con verificaciones de salud explícitas.
- Mantén los trabajos en segundo plano y los consumidores de eventos en sus propios módulos, no dentro de controladores HTTP.
- Haz que la limitación de velocidad, la autenticación y el registro de auditoría sean explícitos para los endpoints públicos.