NestJS, gracias a su arquitectura modular y basada en decoradores, es ideal para implementar DDD y patrones como DTO, Value Objects, Entities y Resources (serializadores). Esto permite un código más limpio, mantenible y desacoplado.
src/
contexts/
user/
domain/
entities/
value-objects/
infrastructure/
repositories/
user.module.ts
apps/
web/
controllers/
dto/
resources/
requests/
web.module.ts
- Propósito: Transportar y validar datos de entrada/salida entre capas.
- Ejemplo:
// apps/web/dto/create-user.dto.ts import { IsString, IsEmail } from 'class-validator'; export class CreateUserDto { @IsString() firstName: string; @IsString() lastName: string; @IsEmail() email: string; }
- Propósito: Representar conceptos inmutables y con reglas de negocio propias en el dominio.
- Ejemplo:
// contexts/user/domain/value-objects/email.vo.ts export class Email { private readonly value: string; constructor(value: string) { if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) { throw new Error('Invalid email'); } this.value = value; } getValue(): string { return this.value; } }
- Propósito: Representar objetos del dominio con identidad y comportamiento.
- Ejemplo:
// contexts/user/domain/entities/user.entity.ts import { Email } from '../value-objects/email.vo'; export class User { constructor( public readonly id: string, public readonly name: string, public readonly email: Email, ) {} }
- Propósito: Transformar entidades o colecciones en respuestas para APIs.
- Ejemplo:
// apps/web/resources/user.resource.ts import { User } from '../../../contexts/user/domain/entities/user.entity'; export class UserResource { static toResponse(user: User) { return { id: user.id, name: user.name, email: user.email.getValue(), }; } }
-
Propósito: Encapsular y centralizar la validación y autorización de una petición HTTP antes de llegar al controlador, similar a los Form Requests de Laravel.
-
Ejemplo:
// apps/web/requests/create-user.request.ts import { Injectable, PipeTransform, ArgumentMetadata, BadRequestException } from '@nestjs/common'; import { plainToInstance } from 'class-transformer'; import { validate } from 'class-validator'; import { CreateUserDto } from '../dto/create-user.dto'; @Injectable() export class CreateUserRequest implements PipeTransform { async transform(value: any, metadata: ArgumentMetadata) { const dto = plainToInstance(CreateUserDto, value); const errors = await validate(dto); if (errors.length > 0) { throw new BadRequestException(errors); } // Aquí puedes agregar lógica de autorización si lo requieres. return dto; } }
Uso en el controlador:
// apps/web/controllers/user.controller.ts import { Body, Controller, Post, UsePipes } from '@nestjs/common'; import { UserService } from '../../../contexts/user/application/user.service'; import { UserResource } from '../resources/user.resource'; import { CreateUserRequest } from '../requests/create-user.request'; @Controller('users') export class UserController { constructor(private readonly userService: UserService) {} @Post() @UsePipes(CreateUserRequest) async create(@Body() dto) { const user = await this.userService.createUser(dto); return UserResource.toResponse(user); } }
// apps/web/controllers/user.controller.ts
import { Body, Controller, Post } from '@nestjs/common';
import { CreateUserDto } from '../dto/create-user.dto';
import { UserService } from '../../../contexts/user/application/user.service';
import { UserResource } from '../resources/user.resource';
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Post()
async create(@Body() dto: CreateUserDto) {
// 1. DTO valida la entrada (class-validator)
// 2. Lógica de dominio
const user = await this.userService.createUser(dto);
// 3. Resource para la respuesta
return UserResource.toResponse(user);
}
}
// contexts/user/application/user.service.ts
import { Injectable } from '@nestjs/common';
import { CreateUserDto } from '../../../apps/web/dto/create-user.dto';
import { User } from '../domain/entities/user.entity';
import { Email } from '../domain/value-objects/email.vo';
@Injectable()
export class UserService {
async createUser(dto: CreateUserDto): Promise<User> {
const name = `${dto.firstName} ${dto.lastName}`;
const email = new Email(dto.email);
// Aquí podrías guardar el usuario en un repositorio
return new User('generated-id', name, email);
}
}
Componente | Propósito | Depende de NestJS | Lógica de negocio |
---|---|---|---|
DTO | Validar/transportar datos | Sí (decoradores) | No |
ValueObject | Concepto del dominio | No | Sí |
Entity | Objeto del dominio | No | Sí |
Resource | Formatear respuesta API | No | No |
Request | Validar/autorización petición | Opcional | No |
Usar DTOs, Value Objects, Entities, Resources y Requests en NestJS permite una arquitectura limpia y desacoplada, ideal para proyectos complejos o con enfoque DDD. Cada componente tiene su responsabilidad y juntos facilitan la evolución y testabilidad del sistema.
Añadir la capa de requests como en Laravel mejora la separación de responsabilidades, centralizando la validación y autorización, y haciendo los controladores aún más limpios.