Manejo de Datos y Capas Adicionales
7. Regla sobre Configuración y Variables de Entorno
Regla Principal
Regla: La configuración de la aplicación (ej. credenciales de base de datos, claves API, puertos) debe gestionarse utilizando el módulo
@nestjs/config.
- Las variables de entorno deben definirse en un archivo
.enven la raíz del proyecto (y este archivo debe estar en.gitignore).- Se debe crear un esquema de validación (usando
Joioclass-validator) para asegurar que todas las variables de entorno requeridas estén presentes y tengan el formato correcto al iniciar la aplicación.- La configuración validada debe cargarse usando
ConfigModule.forRoot()enAppModule, especificandoisGlobal: truepara queConfigServiceesté disponible en toda la aplicación sin necesidad de importarConfigModuleen cada módulo.- El acceso a las variables de configuración debe realizarse exclusivamente a través de
ConfigServiceinyectado en los servicios o providers que lo necesiten. Está prohibido acceder directamente aprocess.envfuera del módulo de configuración.
Contexto y Justificación
Separar la configuración del código es fundamental (principio de Twelve-Factor App).
@nestjs/configproporciona una solución robusta y estandarizada para cargar, validar y acceder a variables de entorno, mejorando la seguridad (evitando hardcodear secretos), la portabilidad entre entornos (desarrollo, producción) y la mantenibilidad.
Ejemplos y Contraejemplos
- Correcto:
// --- .env (No subir a Git) ---
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
PORT=3001
JWT_SECRET=a-very-strong-secret-key
API_KEY_EXTERNAL_SERVICE=xyz789
// --- src/config/validation.schema.ts (usando Joi) ---
import * as Joi from 'joi';
export const validationSchema = Joi.object({
DATABASE_URL: Joi.string().uri().required(),
PORT: Joi.number().default(3000),
JWT_SECRET: Joi.string().min(32).required(),
API_KEY_EXTERNAL_SERVICE: Joi.string().required(),
});
// --- src/app.module.ts ---
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { validationSchema } from './config/validation.schema';
// ... otros imports ...
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true, // Hace ConfigService disponible globalmente
envFilePath: '.env', // Especifica el archivo .env
validationSchema, // Aplica el esquema de validación
validationOptions: {
allowUnknown: true, // Permite otras variables no definidas en el schema
abortEarly: false, // Muestra todos los errores de validación
},
}),
// ... otros módulos ...
],
// ... controllers, providers ...
})
export class AppModule {}
// --- src/modules/database/database.service.ts ---
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class DatabaseService {
private connectionString: string;
constructor(private configService: ConfigService) {
// Accede a la variable validada a través de ConfigService
this.connectionString = this.configService.get<string>('DATABASE_URL');
console.log('Conectando a:', this.connectionString); // ¡Solo para demo!
}
// ... métodos del servicio ...
} - Incorrecto:
// ¡INCORRECTO: Hardcodear secretos!
const dbUrl = 'postgresql://user:password@localhost:5432/mydb';
// ¡INCORRECTO: Acceder directamente a process.env fuera de config!
import { Injectable } from '@nestjs/common';
@Injectable()
export class BadService {
private secret = process.env.JWT_SECRET; // Propenso a errores si no está definida o validada
// ...
}
// Ausencia de ConfigModule o validación en AppModule.
Cuándo Aplicar
Siempre, para cualquier valor que pueda cambiar entre entornos o que sea sensible.
Cuándo Evitar o Flexibilizar
Nunca se debe hardcodear configuración sensible o específica del entorno. Para constantes verdaderamente fijas y no sensibles, se pueden usar constantes en TypeScript, pero la configuración externa debe usar
@nestjs/config.
8. Regla sobre Manejo de Datos con ORM (TypeORM/Prisma)
Regla Principal
Regla: El acceso a la base de datos debe realizarse a través de un ORM (Object-Relational Mapper) como TypeORM o Prisma, siguiendo las mejores prácticas asociadas.
- Módulo Dedicado: La configuración y conexión del ORM deben encapsularse en un módulo dedicado (ej.
DatabaseModule), que luego se importa enAppModuleo módulos específicos.- Patrón Repositorio (Opcional pero Recomendado): Abstraer las consultas específicas del ORM detrás de una interfaz de repositorio (
IUserRepository) e implementar esa interfaz con una clase que utilice el ORM (PrismaUserRepository). Los servicios deben depender de la interfaz del repositorio (Inversión de Dependencias), no directamente del ORM o sus modelos generados.- Entidades/Modelos: Definir entidades (TypeORM) o modelos (Prisma) que representen las tablas de la base de datos. Usar decoradores/schema para definir relaciones, tipos de datos y restricciones.
- Migraciones: Utilizar las herramientas de migración del ORM para gestionar los cambios en el esquema de la base de datos de forma controlada y versionada.
- Transacciones: Utilizar transacciones explícitas del ORM para operaciones que involucren múltiples escrituras y requieran atomicidad.
Contexto y Justificación
Los ORMs simplifican la interacción con la base de datos, proporcionan seguridad contra inyección SQL (si se usan correctamente) y facilitan el mapeo entre objetos de la aplicación y registros de la base de datos. El patrón Repositorio añade una capa de abstracción que desacopla la lógica de negocio del ORM específico, facilitando el testing y la posibilidad de cambiar de ORM o fuente de datos en el futuro. Las migraciones son esenciales para la evolución segura del esquema.
Ejemplos y Contraejemplos
- Correcto (con Prisma y Repositorio):
// --- prisma/schema.prisma ---
model User {
id String @id @default(uuid())
email String @unique
name String?
password String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// --- src/modules/users/interfaces/user.repository.interface.ts ---
import { User } from '@prisma/client';
import { CreateUserPersistenceDto } from '../dtos/persistence.dto'; // DTO específico para persistencia
export interface IUserRepository {
findById(id: string): Promise<User | null>;
findByEmail(email: string): Promise<User | null>;
create(data: CreateUserPersistenceDto): Promise<User>;
}
export const USER_REPOSITORY_TOKEN = Symbol('IUserRepository');
// --- src/modules/users/repositories/prisma-user.repository.ts ---
import { Injectable } from '@nestjs/common';
import { PrismaService } from '@app/database/prisma.service'; // Servicio Prisma centralizado
import { User } from '@prisma/client';
import { IUserRepository } from '../interfaces/user.repository.interface';
import { CreateUserPersistenceDto } from '../dtos/persistence.dto';
@Injectable()
export class PrismaUserRepository implements IUserRepository {
constructor(private readonly prisma: PrismaService) {}
async findById(id: string): Promise<User | null> {
return this.prisma.user.findUnique({ where: { id } });
}
async findByEmail(email: string): Promise<User | null> {
return this.prisma.user.findUnique({ where: { email } });
}
async create(data: CreateUserPersistenceDto): Promise<User> {
return this.prisma.user.create({ data });
}
}
// --- src/modules/users/services/users.service.ts ---
import { Injectable, Inject, NotFoundException } from '@nestjs/common';
import { IUserRepository, USER_REPOSITORY_TOKEN } from '../interfaces/user.repository.interface';
// ... otros imports ...
@Injectable()
export class UsersService {
constructor(
// Depende de la interfaz del repositorio
@Inject(USER_REPOSITORY_TOKEN) private readonly userRepository: IUserRepository
) {}
async getUser(id: string): Promise<User> {
const user = await this.userRepository.findById(id);
if (!user) throw new NotFoundException();
return user;
}
// ... otros métodos usando userRepository ...
}
// --- src/modules/users/users.module.ts ---
import { Module } from '@nestjs/common';
import { PrismaUserRepository } from './repositories/prisma-user.repository';
import { USER_REPOSITORY_TOKEN } from './interfaces/user.repository.interface';
// ... otros imports ...
@Module({
// ... controllers, imports ...
providers: [
UsersService,
{
provide: USER_REPOSITORY_TOKEN, // Proveedor para la interfaz
useClass: PrismaUserRepository, // Usa la implementación concreta
},
],
exports: [UsersService],
})
export class UsersModule {}
// --- src/database/prisma.service.ts (Ejemplo básico) ---
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
async onModuleInit() {
await this.$connect();
}
async onModuleDestroy() {
await this.$disconnect();
}
}
// --- src/database/database.module.ts ---
import { Module, Global } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Global() // Hace PrismaService disponible globalmente
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class DatabaseModule {} - Incorrecto: Servicios interactuando directamente con
PrismaClient(sin repositorio). Construcción manual de queries SQL en strings (vulnerable a inyección). No usar migraciones. Lógica de negocio compleja dentro de la clase del repositorio.
Cuándo Aplicar
Siempre que se interactúe con una base de datos relacional o NoSQL. El patrón Repositorio es altamente recomendado para aplicaciones de medianas a grandes.
Cuándo Evitar o Flexibilizar
El patrón Repositorio puede ser opcional para microservicios muy pequeños o prototipos rápidos, pero el uso del ORM y sus herramientas (migraciones) sigue siendo obligatorio. Si no se usa el repositorio, el servicio interactuará directamente con el cliente del ORM (ej.
PrismaService).
9. Regla sobre Guards
Regla Principal
Regla: La lógica de autorización (verificar si un usuario tiene permiso para acceder a un recurso/ruta) debe implementarse utilizando Guards (
@Injectableque implementaCanActivate).
- Los Guards deben tener una única responsabilidad (ej.
JwtAuthGuardpara verificar token JWT,RolesGuardpara verificar roles de usuario).- Deben retornar
true(oPromise<true>/Observable<true>) si el acceso es permitido, ofalse(oPromise<false>/Observable<false>) si es denegado. También pueden lanzar una excepción (ej.ForbiddenException) para denegar el acceso con un error específico.- Deben obtener información del usuario (ej. desde el token JWT decodificado) a través del objeto
ExecutionContexty su métodoswitchToHttp().getRequest().- Pueden aplicarse a nivel de controlador (
@UseGuards(MyGuard)en la clase) o a nivel de método/ruta (@UseGuards(MyGuard)en el método).- Guards comunes y reutilizables deben ubicarse en
src/common/guards.
Contexto y Justificación
Los Guards proporcionan un mecanismo declarativo y reutilizable para separar la lógica de autorización de los controladores y servicios. Esto mejora la legibilidad, mantenibilidad y testeabilidad, adhiriéndose al principio de Responsabilidad Única. NestJS ejecuta los Guards antes que los Pipes, Interceptors y el propio handler de la ruta.
Ejemplos y Contraejemplos
- Correcto (RolesGuard):
// --- src/common/decorators/roles.decorator.ts ---
import { SetMetadata } from '@nestjs/common';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
// --- src/common/guards/roles.guard.ts ---
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from '../decorators/roles.decorator';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
// Obtiene los roles requeridos definidos con @Roles(...) en la ruta/controlador
const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles) {
return true; // Si no se definieron roles, permite el acceso
}
// Obtiene el usuario del request (asumiendo que JwtAuthGuard ya lo añadió)
const { user } = context.switchToHttp().getRequest();
if (!user || !user.roles) {
throw new ForbiddenException('No tienes permisos para acceder a este recurso.');
}
// Verifica si el usuario tiene al menos uno de los roles requeridos
const hasRole = requiredRoles.some((role) => user.roles?.includes(role));
if (!hasRole) {
throw new ForbiddenException('No tienes los roles necesarios.');
}
return true;
}
}
// --- En el controlador ---
import { Controller, Get, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from './guards/jwt-auth.guard'; // Asume que este guard verifica y añade el user al request
import { RolesGuard } from './guards/roles.guard';
import { Roles } from './decorators/roles.decorator';
@Controller('admin')
// Aplica primero el guard JWT, luego el guard de Roles
@UseGuards(JwtAuthGuard, RolesGuard)
export class AdminController {
@Get('dashboard')
@Roles('admin', 'superadmin') // Solo usuarios con rol 'admin' o 'superadmin' pueden acceder
getDashboard() {
return { message: 'Welcome to the Admin Dashboard!' };
}
@Get('users')
@Roles('superadmin') // Solo 'superadmin'
getUsers() {
// ...
}
} - Incorrecto: Lógica de verificación de roles o permisos dentro de los métodos del controlador o servicio. Guards realizando tareas que no son de autorización (ej. transformación de datos).
Cuándo Aplicar
Siempre que se necesite controlar el acceso a rutas basado en roles, permisos, propiedad de recursos u otras condiciones de autorización.
Cuándo Evitar o Flexibilizar
No usar Guards para lógica de autenticación (verificar identidad del usuario, eso suele hacerse en Middleware o Passport strategies) ni para validación/transformación de datos (usar Pipes).
10. Regla sobre Pipes
Regla Principal
Regla: La validación y transformación de los datos de entrada de una petición (parámetros de ruta, query params, body) debe realizarse utilizando Pipes (
@Injectableque implementaPipeTransform).
- NestJS proporciona Pipes incorporados útiles (
ValidationPipe,ParseIntPipe,ParseUUIDPipe,ParseEnumPipe,DefaultValuePipe) que deben usarse preferentemente.- Para lógica de validación o transformación personalizada y reutilizable, se deben crear Pipes personalizados.
- Un Pipe debe recibir el valor de entrada y el metadata (tipo de parámetro, tipo de dato esperado) y debe retornar el valor transformado o lanzar una excepción (ej.
BadRequestException) si la validación falla.- El
ValidationPipedebe usarse globalmente (ver Norma 6) para la validación basada en DTOs conclass-validator.- Pipes específicos pueden aplicarse a nivel de parámetro (
@Param('id', ParseIntPipe),@Body(MyCustomPipe)).- Pipes personalizados reutilizables deben ubicarse en
src/common/pipes.
Contexto y Justificación
Los Pipes permiten procesar los datos de entrada antes de que lleguen al handler de la ruta. Centralizan la lógica de validación y transformación, manteniendo los controladores limpios y asegurando que los datos recibidos por los servicios sean válidos y tengan el formato esperado. Son ejecutados después de los Guards y Middleware, pero antes de los Interceptors y el handler.
Ejemplos y Contraejemplos
- Correcto (Uso de Pipes incorporados y Global):
// --- src/main.ts ---
// Ver Norma 6 para la configuración de ValidationPipe global
// --- En el controlador ---
import {
Controller, Get, Param, Query, Body, DefaultValuePipe, ParseIntPipe, ParseUUIDPipe, UsePipes, ValidationPipe
} from '@nestjs/common';
import { CreateItemDto } from './dtos/create-item.dto'; // DTO con class-validator
@Controller('items')
export class ItemsController {
@Get(':id')
// ParseUUIDPipe valida que 'id' sea un UUID
findOne(@Param('id', ParseUUIDPipe) id: string) {
console.log(typeof id); // string
// ...
}
@Get()
findAll(
// DefaultValuePipe asigna 10 si 'limit' no se provee
@Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit: number,
// ParseIntPipe convierte y valida que 'offset' sea un entero
@Query('offset', new DefaultValuePipe(0), ParseIntPipe) offset: number,
) {
console.log(typeof limit, typeof offset); // number number
// ...
}
@Post()
// ValidationPipe (aplicado globalmente o aquí) valida el DTO
// @UsePipes(new ValidationPipe({ whitelist: true })) // Si no es global
create(@Body() createItemDto: CreateItemDto) {
// createItemDto está validado
// ...
}
} - Incorrecto: Validación manual de tipos o formatos dentro del controlador (
if (isNaN(parseInt(id))) throw...). Transformación manual de datos dentro del controlador. No usarValidationPipepara DTOs.
Cuándo Aplicar
Siempre para validar y/o transformar parámetros de ruta, query params o el body de la petición.
Cuándo Evitar o Flexibilizar
No usar Pipes para lógica de autorización (usar Guards) o para modificar la respuesta (usar Interceptors).
11. Regla sobre Interceptors
Regla Principal
Regla: La lógica que necesita ejecutarse antes y/o después del handler de la ruta, o que necesita modificar/transformar el resultado antes de enviarlo al cliente, debe implementarse utilizando Interceptors (
@Injectableque implementaNestInterceptor).
- Los Interceptors deben usarse para tareas transversales como: logging de peticiones/respuestas, transformación de la estructura de respuesta, caching de respuestas, manejo de timeouts, serialización de datos salientes (ej. excluir propiedades).
- Un Interceptor tiene acceso al
ExecutionContext(como los Guards) y alCallHandler, que permite interactuar con el flujo de la respuesta (a través dehandle()que devuelve unObservable).- Se deben usar operadores de RxJS (como
map,tap,catchError) dentro del métodointerceptpara manipular el stream de respuesta.- Pueden aplicarse globalmente (
app.useGlobalInterceptors), a nivel de controlador (@UseInterceptors) o a nivel de método.- Interceptores reutilizables deben ubicarse en
src/common/interceptors.- El
ClassSerializerInterceptordebe usarse globalmente si se utilizaclass-transformerpara controlar la serialización de entidades/DTOs salientes (ej. con@Exclude,@Expose).
Contexto y Justificación
Los Interceptors proporcionan un potente mecanismo AOP (Aspect-Oriented Programming) para añadir comportamiento alrededor de la ejecución de los handlers. Permiten mantener los controladores y servicios enfocados en su lógica principal, mientras que tareas transversales como el formateo de respuestas o el logging se manejan de forma separada y reutilizable.
Ejemplos y Contraejemplos
- Correcto (Transformación de Respuesta y Serialización):
// --- src/common/interceptors/transform.interceptor.ts ---
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
export interface Response<T> {
statusCode: number;
message: string;
data: T;
}
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> {
const statusCode = context.switchToHttp().getResponse().statusCode;
return next.handle().pipe(
map(data => ({
statusCode: statusCode,
message: 'Success', // Podría ser más dinámico
data: data, // Envuelve la data original
})),
);
}
}
// --- src/modules/users/entities/user.entity.ts (Ejemplo con Exclude) ---
import { Exclude } from 'class-transformer';
export class User {
id: string;
email: string;
name: string;
@Exclude() // Excluye la contraseña de la respuesta JSON
passwordHash: string;
constructor(partial: Partial<User>) {
Object.assign(this, partial);
}
}
// --- src/main.ts ---
import { NestFactory, Reflector } from '@nestjs/core';
import { AppModule } from './app.module';
import { ClassSerializerInterceptor, ValidationPipe } from '@nestjs/common';
import { TransformInterceptor } from './common/interceptors/transform.interceptor';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe(/* ... */));
// Aplica ClassSerializerInterceptor para @Exclude/@Expose
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));
// Aplica el interceptor de transformación de respuesta globalmente
app.useGlobalInterceptors(new TransformInterceptor());
await app.listen(3000);
}
bootstrap();
// --- En el controlador (El handler devuelve la entidad User) ---
import { Controller, Get, Param, ParseUUIDPipe } from '@nestjs/common';
import { UsersService } from './services/users.service';
import { User } from './entities/user.entity';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get(':id')
async findOne(@Param('id', ParseUUIDPipe) id: string): Promise<User> {
// El servicio devuelve una instancia de User (con passwordHash)
return this.usersService.findUserById(id);
}
}
// La respuesta final será:
// {
// "statusCode": 200,
// "message": "Success",
// "data": {
// "id": "...",
// "email": "...",
// "name": "..."
// // passwordHash está excluido gracias a ClassSerializerInterceptor y @Exclude
// }
// } - Incorrecto: Formatear manualmente la estructura de respuesta dentro de cada método del controlador. Realizar logging extenso dentro de los servicios. Usar interceptors para validación (usar Pipes) o autorización (usar Guards).
Cuándo Aplicar
Para logging, transformación/estandarización de respuestas, caching, serialización controlada de datos salientes, manejo de timeouts.
Cuándo Evitar o Flexibilizar
No usar para lógica de negocio principal, validación de entrada o autorización.
12. Regla sobre Filtros de Excepción
Regla Principal
Regla: El manejo centralizado de excepciones no controladas y la personalización del formato de respuesta de errores deben realizarse utilizando Filtros de Excepción (
@Catchdecorador en una clase que implementaExceptionFilter).
- NestJS tiene un filtro de excepciones global base que maneja excepciones de tipo
HttpExceptiony cualquier otra excepción no controlada (resultando en un 500 Internal Server Error genérico).- Se deben crear filtros personalizados para: capturar tipos específicos de excepciones (incluyendo errores que no heredan de
HttpException), loggear errores de forma centralizada, y/o estandarizar el formato de la respuesta de error JSON.- Un filtro personalizado debe implementar la interfaz
ExceptionFiltery su métodocatch(exception: T, host: ArgumentsHost). Dentro decatch, se obtiene acceso a los objetosrequestyresponsepara enviar una respuesta personalizada.- Los filtros pueden capturar una o más tipos de excepciones específicas (
@Catch(MyException, YourException)) o todas las excepciones (@Catch()).- Pueden aplicarse globalmente (
app.useGlobalFilters), a nivel de controlador (@UseFilters) o a nivel de método.- Filtros reutilizables deben ubicarse en
src/common/filters.
Contexto y Justificación
Los Filtros de Excepción proporcionan un lugar único para manejar los errores que ocurren durante el ciclo de vida de la petición. Permiten separar la lógica de manejo de errores del código principal (controladores, servicios), estandarizar las respuestas de error que ve el cliente y realizar logging de errores de forma consistente.
Ejemplos y Contraejemplos
- Correcto (Filtro Global para Errores HTTP y Otros):
// --- src/common/filters/all-exceptions.filter.ts ---
import {
ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus,
} from '@nestjs/common';
import { HttpAdapterHost } from '@nestjs/core'; // Para compatibilidad platform-agnostic
import { Request, Response } from 'express'; // O importar de fastify si se usa
@Catch() // Captura todas las excepciones
export class AllExceptionsFilter implements ExceptionFilter {
constructor(private readonly httpAdapterHost: HttpAdapterHost) {}
catch(exception: unknown, host: ArgumentsHost): void {
const { httpAdapter } = this.httpAdapterHost;
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const httpStatus =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
let errorMessage = 'Internal server error';
let errorDetails: any = undefined;
if (exception instanceof HttpException) {
const errorResponse = exception.getResponse();
errorMessage = typeof errorResponse === 'string' ? errorResponse : (errorResponse as any).message || exception.message;
if (typeof errorResponse === 'object' && (errorResponse as any).error) {
errorDetails = (errorResponse as any).error;
}
} else if (exception instanceof Error) {
// Podrías querer loggear el stack trace aquí para errores no-HTTP
console.error('[AllExceptionsFilter] Non-HTTP Error:', exception);
errorMessage = exception.message || errorMessage;
// No exponer stack trace en producción
// errorDetails = process.env.NODE_ENV !== 'production' ? exception.stack : undefined;
}
const responseBody = {
statusCode: httpStatus,
timestamp: new Date().toISOString(),
path: httpAdapter.getRequestUrl(request),
method: httpAdapter.getRequestMethod(request),
message: errorMessage,
...(errorDetails && { details: errorDetails }), // Añade detalles si existen
};
// Loggear el error aquí si es necesario (usando un LoggerService inyectado)
// this.logger.error(`[${request.method}] ${request.url} - Status: ${httpStatus}`, exception.stack);
httpAdapter.reply(response, responseBody, httpStatus);
}
}
// --- src/main.ts ---
import { HttpAdapterHost, NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { AllExceptionsFilter } from './common/filters/all-exceptions.filter';
// ... otros imports
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// ... otros app.use ...
const httpAdapterHost = app.get(HttpAdapterHost);
app.useGlobalFilters(new AllExceptionsFilter(httpAdapterHost));
await app.listen(3000);
}
bootstrap(); - Incorrecto: Bloques
try/catchmasivos en los controladores para formatear errores. Devolver mensajes de error inconsistentes desde diferentes partes de la aplicación. No manejar explícitamente excepciones específicas del dominio si requieren un tratamiento particular.
Cuándo Aplicar
Siempre se debe tener al menos el filtro base de NestJS. Crear filtros personalizados cuando se necesite estandarizar el formato de error, loggear errores centralizadamente o manejar tipos específicos de excepciones de forma diferente.
Cuándo Evitar o Flexibilizar
Para aplicaciones muy simples, el filtro base puede ser suficiente si no se requiere un formato de error específico o logging centralizado. Sin embargo, un filtro global personalizado es una buena práctica general.