Requests and responses

Input and output DTOs with ObjectClass, AutoMap, and Swagger decorators.

The application layer defines input and output contracts for each operation. These classes feed both mapping and OpenAPI documentation.

Create requests extend ObjectClass and use @ApiProperty() for Swagger and @AutoMap() for mapping:

typescript
export class CreatePersonAddressRequest extends ObjectClass<CreatePersonAddressRequest> {
  @ApiProperty({ example: '123 Main St' })
  @AutoMap()
  address: string;
}

export class CreatePersonContactRequest extends ObjectClass<CreatePersonContactRequest> {
  @ApiProperty({ example: 'john.doe@example.com' })
  @AutoMap()
  contact: string;
}

export class CreatePersonRequest extends ObjectClass<CreatePersonRequest> {
  @ApiProperty({ example: 'John Doe' })
  @AutoMap()
  name: string;

  @ApiProperty({ type: CreatePersonAddressRequest })
  @AutoMap()
  address: CreatePersonAddressRequest;

  @ApiProperty({ type: CreatePersonContactRequest, isArray: true })
  @AutoMap({ type: () => CreatePersonContactRequest })
  contacts: CreatePersonContactRequest[];
}

Minimal responses return only the generated identifier:

typescript
import { CreatedRegistreWithIdResponse } from '@/application/common/created-registre.response';

export class CreatePersonResponse extends CreatedRegistreWithIdResponse {}

For new resources, reuse CreatedRegistreWithIdResponse or CreatedRegistreWithUUIDResponse from created-registre.response.ts.

Update requests are plain classes — they do not extend ObjectClass:

typescript
// src/application/person/update/update-person.request.ts
export class UpdatePersonRequest {
  @ApiProperty({ example: 1 })
  @AutoMap()
  id: number;

  @ApiProperty({ example: 'John Doe' })
  @AutoMap()
  name: string;

  @ApiProperty({ type: () => UpdatePersonAddressRequest })
  @AutoMap()
  address: UpdatePersonAddressRequest;

  @ApiProperty({ type: () => UpdatePersonContactRequest, isArray: true })
  @AutoMap({ type: () => UpdatePersonContactRequest })
  contacts: UpdatePersonContactRequest[];
}

Read responses expose the full model (or a subset) mapped from the entity:

typescript
// src/application/person/read/read-person.response.ts
export class ReadPersonResponse {
  @ApiProperty({ example: 1 })
  @AutoMap()
  id: number;

  @ApiProperty({ example: 'John Doe' })
  @AutoMap()
  name: string;

  @ApiProperty({ type: () => ReadPersonAddressResponse })
  @AutoMap()
  address: ReadPersonAddressResponse;

  @ApiProperty({ type: () => ReadPersonContactResponse, isArray: true })
  @AutoMap({ type: () => ReadPersonContactResponse })
  contacts: ReadPersonContactResponse[];
}

Listings extend ListResponseBase (already exposes items and count):

typescript
export class ReadManyPersonResponseItem {
  @ApiProperty()
  @AutoMap()
  id: number;

  @ApiProperty()
  @AutoMap()
  name: string;

  @ApiProperty()
  @AutoMap()
  active: boolean;
}

export class ReadManyPersonResponse extends ListResponseBase<ReadManyPersonResponseItem> {}

In the handler: ReadManyPersonResponse.from({ items, count }).

Extend PaginationRequest to inherit pagination parameters:

typescript
export class ReadManyPersonRequest extends PaginationRequest {
  @ApiProperty()
  @AutoMap()
  name?: string;
}

Organize by resource and operation:

text
src/application/person/
├── create/       # request, response, handler, validator
├── read/         # handler, response
├── read-many/    # request, response, handler, validator
├── update/       # request, handler, validator
├── delete/       # handler
└── jobs/
    ├── cron/     # CronJobHandlerBase
    └── events/   # EventJob + handlers by specialty