Every document in the imperial chancellery had to have precisely described fields - the petitioner's name, the type of matter, the date, and the seal. In NestJS, the equivalent of these documents are DTOs (Data Transfer Objects), and the
@ApiProperty() decorator describes each field so that Swagger UI can display it.The
@ApiProperty() decorator describes a single field in a DTO class:1import { ApiProperty } from '@nestjs/swagger';
2
3export class CreateLegionaryDto {
4 @ApiProperty({
5 description: 'Name of the legionary',
6 example: 'Marcus Aurelius',
7 })
8 name: string;
9
10 @ApiProperty({
11 description: 'Rank in the legion',
12 example: 'Centurio',
13 })
14 rank: string;
15
16 @ApiProperty({
17 description: 'Years of experience',
18 example: 5,
19 minimum: 0,
20 maximum: 40,
21 })
22 experience: number;
23}The decorator accepts many configuration options:
| Option | Description | Example | |--------|-------------|---------| |
description | Field description | 'Name of the legionary' |
| example | Example value | 'Marcus' |
| required | Whether the field is required | true / false |
| default | Default value | 'Miles' |
| enum | List of allowed values | ['Centurio', 'Miles'] |
| type | Field type | String, Number, Boolean |
| isArray | Whether the field is an array | true |
| minimum | Minimum value (number) | 0 |
| maximum | Maximum value (number) | 100 |
| minLength | Minimum length (string) | 2 |
| maxLength | Maximum length (string) | 50 |For optional fields, we use
@ApiPropertyOptional():1import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
2
3export class UpdateLegionaryDto {
4 @ApiPropertyOptional({
5 description: 'New rank of the legionary',
6 example: 'Optio',
7 })
8 rank?: string;
9
10 @ApiPropertyOptional({
11 description: 'New assigned legion',
12 example: 'Legio III Augusta',
13 })
14 legio?: string;
15
16 @ApiPropertyOptional({
17 description: 'Additional experience',
18 example: 2,
19 minimum: 0,
20 })
21 additionalExperience?: number;
22}Enum is particularly useful for fields with a limited set of values:
1export enum LegionaryRank {
2 MILES = 'Miles',
3 OPTIO = 'Optio',
4 CENTURIO = 'Centurio',
5 TRIBUNUS = 'Tribunus',
6 LEGATUS = 'Legatus',
7}
8
9export class CreateLegionaryDto {
10 @ApiProperty({
11 description: 'Rank of the legionary',
12 enum: LegionaryRank,
13 example: LegionaryRank.MILES,
14 })
15 rank: LegionaryRank;
16}For nested DTOs, we use the
type option:1export class AddressDto {
2 @ApiProperty({ description: 'Province name', example: 'Pannonia' })
3 province: string;
4
5 @ApiProperty({ description: 'City name', example: 'Aquincum' })
6 city: string;
7}
8
9export class LegionDto {
10 @ApiProperty({ description: 'Legion name', example: 'Legio X Gemina' })
11 name: string;
12
13 @ApiProperty({
14 description: 'Legion location',
15 type: AddressDto,
16 })
17 location: AddressDto;
18
19 @ApiProperty({
20 description: 'List of centurions',
21 type: [String],
22 example: ['Marcus', 'Julius', 'Gaius'],
23 })
24 centurions: string[];
25}It is a good practice to create separate DTOs for API responses:
1export class LegionaryResponseDto {
2 @ApiProperty({ description: 'Legionary ID', example: '507f1f77bcf86cd799439011' })
3 id: string;
4
5 @ApiProperty({ description: 'Name of the legionary', example: 'Marcus Aurelius' })
6 name: string;
7
8 @ApiProperty({ description: 'Rank', enum: LegionaryRank })
9 rank: LegionaryRank;
10
11 @ApiProperty({ description: 'Recruitment date', example: '2024-01-15T10:30:00Z' })
12 recruitedAt: Date;
13
14 @ApiProperty({ description: 'Is active', example: true })
15 isActive: boolean;
16}Create DTO documentation for the Empire's province management system: