Skip to content

Instantly share code, notes, and snippets.

@frfernandezdev
Last active June 9, 2025 14:28
Show Gist options
  • Save frfernandezdev/1b7cf62e67a84d883eb4f9618ead164f to your computer and use it in GitHub Desktop.
Save frfernandezdev/1b7cf62e67a84d883eb4f9618ead164f to your computer and use it in GitHub Desktop.
Software Design Docs Stack Backend

Aplicación de DDD, DTOs, Value Objects, Entities y Resources en NestJS

1. Introducción

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.


2. Estructura recomendada

src/
  contexts/
    user/
      domain/
        entities/
        value-objects/
      infrastructure/
        repositories/
      user.module.ts
  apps/
    web/
      controllers/
      dto/
      resources/
      requests/
      web.module.ts

3. Componentes y su propósito

3.1. DTO (Data Transfer Object)

  • 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;
    }

3.2. Value Object

  • 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;
      }
    }

3.3. Entity

  • 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,
      ) {}
    }

3.4. Resource (Serializador/Clase de respuesta)

  • 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(),
        };
      }
    }

3.5. Request (Inspirado en Laravel Form Requests)

  • 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);
      }
    }

4. Flujo típico en un controlador

// 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);
  }
}

5. Service de aplicación y uso de Entity/Value Object

// 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);
  }
}

6. Diferencias clave

Componente Propósito Depende de NestJS Lógica de negocio
DTO Validar/transportar datos Sí (decoradores) No
ValueObject Concepto del dominio No
Entity Objeto del dominio No
Resource Formatear respuesta API No No
Request Validar/autorización petición Opcional No

7. Conclusión

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment