Add request DTO — append to presentation/dto/{module}.dto.ts
- Zod schema +
@ZodSchema() for request body/params/query
@ApiProperty() on every property
- Follow naming:
{Action}{Module}BodyDto, {Action}{Module}ParamsDto, {Action}{Module}QueryDto
Request body DTO template:
export const {Action}{Entity}BodySchema = z.object({
name: z.string().min(1).max(100),
});
@ZodSchema({Action}{Entity}BodySchema)
export class {Action}{Entity}BodyDto {
@ApiProperty({ example: "My entity", description: "Display name", minLength: 1, maxLength: 100 })
name!: string;
}
Path params DTO template:
export const Get{Entity}ParamsSchema = z.object({
id: z.string().uuid(),
});
@ZodSchema(Get{Entity}ParamsSchema)
export class Get{Entity}ParamsDto {
@ApiProperty({ example: "550e8400-e29b-41d4-a716-446655440000", description: "{Entity} identifier" })
id!: string;
}
Query params DTO template (pagination):
export const List{Entity}QuerySchema = z.object({
page: z.coerce.number().int().positive().optional(),
limit: z.coerce.number().int().positive().max(100).optional(),
});
@ZodSchema(List{Entity}QuerySchema)
export class List{Entity}QueryDto {
@ApiProperty({ example: 1, description: "Page number", required: false })
page?: number;
@ApiProperty({ example: 20, description: "Items per page", required: false })
limit?: number;
}
Add endpoint method — append to the controller class
@CorrelationId() parameter — required on every endpoint
- Full Swagger decoration:
@ApiOperation, @ApiResponse, @ApiBody/@ApiParam/@ApiQuery
- Command endpoints:
@HttpCode(HttpStatus.NO_CONTENT)
- Add necessary imports at the top of the controller file
Command endpoint (POST — 204):
@Post()
@HttpCode(HttpStatus.NO_CONTENT)
@ApiOperation({ summary: "Create a {entity}" })
@ApiBody({ type: Create{Entity}BodyDto })
@ApiResponse({ status: 204, description: "Created successfully" })
@ApiResponse({ status: 422, description: "Validation error", type: ErrorResponseDto })
async create(
@Body() body: Create{Entity}BodyDto,
@CorrelationId() correlationId: string,
): Promise<void> {
await this.commandBus.execute(new Create{Entity}Command({ ...body, correlationId }));
}
Command endpoint (PATCH — 204):
@Patch("/:id")
@HttpCode(HttpStatus.NO_CONTENT)
@ApiOperation({ summary: "Update a {entity}" })
@ApiParam({ name: "id", type: String, description: "{Entity} identifier" })
@ApiBody({ type: Update{Entity}BodyDto })
@ApiResponse({ status: 204, description: "Updated successfully" })
@ApiResponse({ status: 404, description: "Not found", type: ErrorResponseDto })
async update(
@Param("id") id: string,
@Body() body: Update{Entity}BodyDto,
@CorrelationId() correlationId: string,
): Promise<void> {
await this.commandBus.execute(new Update{Entity}Command({ id, ...body, correlationId }));
}
Command endpoint (DELETE — 204):
@Delete("/:id")
@HttpCode(HttpStatus.NO_CONTENT)
@ApiOperation({ summary: "Delete a {entity}" })
@ApiParam({ name: "id", type: String, description: "{Entity} identifier" })
@ApiResponse({ status: 204, description: "Deleted successfully" })
@ApiResponse({ status: 404, description: "Not found", type: ErrorResponseDto })
async delete(
@Param("id") id: string,
@CorrelationId() correlationId: string,
): Promise<void> {
await this.commandBus.execute(new Delete{Entity}Command({ id, correlationId }));
}
Query endpoint (GET single — 200):
@Get("/:id")
@ApiOperation({ summary: "Get {entity} by ID" })
@ApiParam({ name: "id", type: String, description: "{Entity} identifier" })
@ApiResponse({ status: 200, description: "Found", type: {Entity}ResponseDto })
@ApiResponse({ status: 404, description: "Not found", type: ErrorResponseDto })
async getById(
@Param("id") id: string,
@CorrelationId() correlationId: string,
): Promise<{Entity}ResponseDto> {
const result = await this.queryBus.execute(new Get{Entity}Query({ id, correlationId }));
if (!result) throw new NotFoundException();
return { id: result.id };
}
Query endpoint (GET paginated — 200):
@Get()
@ApiOperation({ summary: "List {entity}s" })
@ApiQuery({ name: "page", type: Number, required: false, description: "Page number (default: 1)" })
@ApiQuery({ name: "limit", type: Number, required: false, description: "Items per page (default: 20)" })
@ApiResponse({ status: 200, description: "Paginated list", type: List{Entity}ResponseDto })
async list(
@Query() query: List{Entity}QueryDto,
@CorrelationId() correlationId: string,
): Promise<List{Entity}ResponseDto> {
return this.queryBus.execute(new List{Entity}Query({ ...query, correlationId }));
}
Required imports:
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Patch, Post, Query } from "@nestjs/common";
import { ApiBody, ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags } from "@nestjs/swagger";
import { ErrorResponseDto } from "{SHARED_ROOT}/errors/base-feature-exception.filter";
import { CorrelationId } from "{SHARED_ROOT}/decorators/correlation-id.decorator";