0
# Type Helpers
1
2
The **@nestjs/swagger** type helpers provide powerful utility functions for creating derived types and DTOs. These functions are essential for building maintainable, type-safe APIs by allowing you to create new classes based on existing ones while preserving all OpenAPI metadata, validation rules, and transformation logic.
3
4
## Overview
5
6
Type helpers extend the functionality from `@nestjs/mapped-types` with full OpenAPI/Swagger integration, ensuring that derived types automatically inherit proper API documentation metadata.
7
8
```typescript { .api }
9
import {
10
PartialType,
11
PickType,
12
OmitType,
13
IntersectionType
14
} from '@nestjs/swagger';
15
```
16
17
All type helpers work by:
18
1. Creating a new class that extends or modifies the original class structure
19
2. Inheriting validation metadata from class-validator decorators
20
3. Inheriting transformation metadata from class-transformer decorators
21
4. Copying and adapting OpenAPI metadata for proper schema generation
22
5. Maintaining proper TypeScript type inference
23
24
## PartialType
25
26
### PartialType { .api }
27
28
```typescript
29
function PartialType<T>(
30
classRef: Type<T>,
31
options?: {
32
skipNullProperties?: boolean;
33
}
34
): Type<Partial<T>>
35
```
36
37
Creates a new type where all properties of the input type are optional. This is particularly useful for creating update DTOs where only some fields may be provided.
38
39
**Parameters:**
40
- `classRef`: The base class to make partial
41
- `options.skipNullProperties`: If `false`, validations ignore only `undefined` values. If `true` (default), validations ignore both `null` and `undefined` values.
42
43
**Type Behavior:**
44
- All properties become optional (`prop?: type`)
45
- All OpenAPI properties get `required: false`
46
- Validation decorators are modified to allow undefined/null values
47
- Original property types and constraints are preserved
48
49
**Basic Usage:**
50
```typescript
51
import { ApiProperty } from '@nestjs/swagger';
52
import { IsString, IsNumber, IsEmail } from 'class-validator';
53
54
class CreateUserDto {
55
@ApiProperty({ description: 'User name' })
56
@IsString()
57
name: string;
58
59
@ApiProperty({ description: 'User email' })
60
@IsEmail()
61
email: string;
62
63
@ApiProperty({ description: 'User age' })
64
@IsNumber()
65
age: number;
66
}
67
68
// All properties become optional
69
class UpdateUserDto extends PartialType(CreateUserDto) {}
70
71
// UpdateUserDto is equivalent to:
72
class UpdateUserDto {
73
@ApiProperty({ description: 'User name', required: false })
74
@IsOptional()
75
@IsString()
76
name?: string;
77
78
@ApiProperty({ description: 'User email', required: false })
79
@IsOptional()
80
@IsEmail()
81
email?: string;
82
83
@ApiProperty({ description: 'User age', required: false })
84
@IsOptional()
85
@IsNumber()
86
age?: number;
87
}
88
```
89
90
**Advanced Usage with Options:**
91
```typescript
92
// Default behavior: ignore null and undefined
93
class UpdateUserDto extends PartialType(CreateUserDto) {}
94
95
// Custom behavior: only ignore undefined (null values are validated)
96
class StrictUpdateUserDto extends PartialType(CreateUserDto, {
97
skipNullProperties: false
98
}) {}
99
100
// Usage in controller
101
@Controller('users')
102
export class UsersController {
103
@Patch(':id')
104
@ApiOperation({ summary: 'Update user' })
105
@ApiResponse({ status: 200, type: User })
106
update(
107
@Param('id') id: string,
108
@Body() updateUserDto: UpdateUserDto
109
) {
110
// Only provided fields will be updated
111
return this.usersService.update(id, updateUserDto);
112
}
113
}
114
```
115
116
## PickType
117
118
### PickType { .api }
119
120
```typescript
121
function PickType<T, K extends keyof T>(
122
classRef: Type<T>,
123
keys: readonly K[]
124
): Type<Pick<T, (typeof keys)[number]>>
125
```
126
127
Creates a new type by selecting (picking) specific properties from the input type. Useful for creating focused DTOs that only contain relevant fields.
128
129
**Parameters:**
130
- `classRef`: The base class to pick properties from
131
- `keys`: Array of property names to include in the new type
132
133
**Type Behavior:**
134
- Only specified properties are included
135
- All metadata (validation, transformation, OpenAPI) is preserved for picked properties
136
- Properties maintain their original required/optional status
137
- TypeScript enforces that keys exist on the original type
138
139
**Basic Usage:**
140
```typescript
141
class User {
142
@ApiProperty({ description: 'Unique user ID' })
143
id: string;
144
145
@ApiProperty({ description: 'User name' })
146
@IsString()
147
name: string;
148
149
@ApiProperty({ description: 'User email' })
150
@IsEmail()
151
email: string;
152
153
@ApiProperty({ description: 'User password' })
154
@IsString()
155
@MinLength(8)
156
password: string;
157
158
@ApiProperty({ description: 'Creation date' })
159
createdAt: Date;
160
161
@ApiProperty({ description: 'Last update date' })
162
updatedAt: Date;
163
}
164
165
// Pick only public profile information
166
class UserProfileDto extends PickType(User, ['id', 'name', 'email'] as const) {}
167
168
// UserProfileDto is equivalent to:
169
class UserProfileDto {
170
@ApiProperty({ description: 'Unique user ID' })
171
id: string;
172
173
@ApiProperty({ description: 'User name' })
174
@IsString()
175
name: string;
176
177
@ApiProperty({ description: 'User email' })
178
@IsEmail()
179
email: string;
180
}
181
```
182
183
**Practical Examples:**
184
```typescript
185
// Login credentials (pick only auth-related fields)
186
class LoginDto extends PickType(User, ['email', 'password'] as const) {}
187
188
// User summary for lists (pick minimal fields)
189
class UserSummaryDto extends PickType(User, ['id', 'name'] as const) {}
190
191
// Admin view (pick all except sensitive data)
192
class AdminUserDto extends PickType(User, [
193
'id', 'name', 'email', 'createdAt', 'updatedAt'
194
] as const) {}
195
196
@Controller('users')
197
export class UsersController {
198
@Get('profile')
199
@ApiResponse({ status: 200, type: UserProfileDto })
200
getProfile(): UserProfileDto {
201
// Return only public profile data
202
}
203
204
@Post('login')
205
@ApiBody({ type: LoginDto })
206
@ApiResponse({ status: 200, description: 'Login successful' })
207
login(@Body() loginDto: LoginDto) {
208
// Only email and password are accepted
209
}
210
}
211
```
212
213
## OmitType
214
215
### OmitType { .api }
216
217
```typescript
218
function OmitType<T, K extends keyof T>(
219
classRef: Type<T>,
220
keys: readonly K[]
221
): Type<Omit<T, (typeof keys)[number]>>
222
```
223
224
Creates a new type by excluding (omitting) specific properties from the input type. Useful for removing sensitive or computed fields from DTOs.
225
226
**Parameters:**
227
- `classRef`: The base class to omit properties from
228
- `keys`: Array of property names to exclude from the new type
229
230
**Type Behavior:**
231
- Specified properties are excluded from the new type
232
- All other properties maintain their metadata and constraints
233
- TypeScript ensures omitted keys exist on the original type
234
- Remaining properties keep their original required/optional status
235
236
**Basic Usage:**
237
```typescript
238
class User {
239
@ApiProperty({ description: 'Unique user ID' })
240
id: string;
241
242
@ApiProperty({ description: 'User name' })
243
@IsString()
244
name: string;
245
246
@ApiProperty({ description: 'User email' })
247
@IsEmail()
248
email: string;
249
250
@ApiProperty({ description: 'User password' })
251
@IsString()
252
@MinLength(8)
253
password: string;
254
255
@ApiProperty({ description: 'Internal user role' })
256
role: string;
257
}
258
259
// Create user DTO without ID (auto-generated) and role (set by system)
260
class CreateUserDto extends OmitType(User, ['id', 'role'] as const) {}
261
262
// CreateUserDto is equivalent to:
263
class CreateUserDto {
264
@ApiProperty({ description: 'User name' })
265
@IsString()
266
name: string;
267
268
@ApiProperty({ description: 'User email' })
269
@IsEmail()
270
email: string;
271
272
@ApiProperty({ description: 'User password' })
273
@IsString()
274
@MinLength(8)
275
password: string;
276
}
277
```
278
279
**Practical Examples:**
280
```typescript
281
// Response DTO without sensitive information
282
class UserResponseDto extends OmitType(User, ['password'] as const) {}
283
284
// Public API DTO without internal fields
285
class PublicUserDto extends OmitType(User, ['password', 'role'] as const) {}
286
287
// Registration DTO without system-managed fields
288
class RegisterDto extends OmitType(User, ['id', 'role', 'createdAt', 'updatedAt'] as const) {}
289
290
@Controller('users')
291
export class UsersController {
292
@Post('register')
293
@ApiBody({ type: RegisterDto })
294
@ApiResponse({ status: 201, type: UserResponseDto })
295
register(@Body() registerDto: RegisterDto): UserResponseDto {
296
// Password and role are excluded from response
297
const user = this.usersService.create(registerDto);
298
return omit(user, ['password']);
299
}
300
301
@Get(':id')
302
@ApiResponse({ status: 200, type: PublicUserDto })
303
findOne(@Param('id') id: string): PublicUserDto {
304
// Internal fields are automatically excluded
305
return this.usersService.findPublicInfo(id);
306
}
307
}
308
```
309
310
## IntersectionType
311
312
### IntersectionType { .api }
313
314
```typescript
315
function IntersectionType<T extends Type[]>(
316
...classRefs: T
317
): Intersection<T>
318
```
319
320
Creates a new type by combining (intersecting) multiple classes. The resulting type contains all properties from all input classes, making it useful for composing DTOs from multiple base classes.
321
322
**Parameters:**
323
- `...classRefs`: Variable number of classes to combine
324
325
**Type Behavior:**
326
- All properties from all input classes are merged into the new type
327
- If properties have the same name, the last class in the parameter list takes precedence
328
- All validation and transformation metadata is inherited
329
- OpenAPI metadata is merged appropriately
330
331
**Type Definitions:**
332
```typescript
333
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
334
335
type ClassRefsToConstructors<T extends Type[]> = {
336
[U in keyof T]: T[U] extends Type<infer V> ? V : never;
337
};
338
339
type Intersection<T extends Type[]> = Type<UnionToIntersection<ClassRefsToConstructors<T>[number]>>;
340
```
341
342
**Basic Usage:**
343
```typescript
344
class BaseDto {
345
@ApiProperty({ description: 'Record ID' })
346
@IsString()
347
id: string;
348
349
@ApiProperty({ description: 'Creation timestamp' })
350
createdAt: Date;
351
}
352
353
class UserInfoDto {
354
@ApiProperty({ description: 'User name' })
355
@IsString()
356
@MinLength(2)
357
name: string;
358
359
@ApiProperty({ description: 'User email' })
360
@IsEmail()
361
email: string;
362
}
363
364
class ContactDto {
365
@ApiProperty({ description: 'Phone number', required: false })
366
@IsOptional()
367
@IsPhoneNumber()
368
phone?: string;
369
370
@ApiProperty({ description: 'Address', required: false })
371
@IsOptional()
372
@IsString()
373
address?: string;
374
}
375
376
// Combine all three classes into a complete user DTO
377
class CompleteUserDto extends IntersectionType(BaseDto, UserInfoDto, ContactDto) {}
378
379
// CompleteUserDto contains all properties:
380
class CompleteUserDto {
381
@ApiProperty({ description: 'Record ID' })
382
@IsString()
383
id: string;
384
385
@ApiProperty({ description: 'Creation timestamp' })
386
createdAt: Date;
387
388
@ApiProperty({ description: 'User name' })
389
@IsString()
390
@MinLength(2)
391
name: string;
392
393
@ApiProperty({ description: 'User email' })
394
@IsEmail()
395
email: string;
396
397
@ApiProperty({ description: 'Phone number', required: false })
398
@IsOptional()
399
@IsPhoneNumber()
400
phone?: string;
401
402
@ApiProperty({ description: 'Address', required: false })
403
@IsOptional()
404
@IsString()
405
address?: string;
406
}
407
```
408
409
**Complex Composition Examples:**
410
```typescript
411
// Base classes for different aspects
412
class TimestampDto {
413
@ApiProperty({ description: 'Creation date' })
414
createdAt: Date;
415
416
@ApiProperty({ description: 'Last update date' })
417
updatedAt: Date;
418
}
419
420
class OwnershipDto {
421
@ApiProperty({ description: 'Creator user ID' })
422
@IsString()
423
createdBy: string;
424
425
@ApiProperty({ description: 'Last modifier user ID' })
426
@IsString()
427
updatedBy: string;
428
}
429
430
class StatusDto {
431
@ApiProperty({
432
description: 'Record status',
433
enum: ['active', 'inactive', 'pending', 'archived']
434
})
435
@IsEnum(['active', 'inactive', 'pending', 'archived'])
436
status: string;
437
}
438
439
// Product-specific information
440
class ProductDataDto {
441
@ApiProperty({ description: 'Product name' })
442
@IsString()
443
@MinLength(1)
444
name: string;
445
446
@ApiProperty({ description: 'Product description' })
447
@IsString()
448
description: string;
449
450
@ApiProperty({ description: 'Product price' })
451
@IsNumber()
452
@Min(0)
453
price: number;
454
}
455
456
// Complete product DTO with all metadata
457
class ProductDto extends IntersectionType(
458
ProductDataDto,
459
TimestampDto,
460
OwnershipDto,
461
StatusDto
462
) {}
463
464
// Create DTO that omits system-managed fields
465
class CreateProductDto extends IntersectionType(
466
ProductDataDto,
467
PickType(StatusDto, ['status'] as const)
468
) {}
469
470
// Update DTO with partial product data and ownership
471
class UpdateProductDto extends IntersectionType(
472
PartialType(ProductDataDto),
473
OwnershipDto
474
) {}
475
```
476
477
## Combining Type Helpers
478
479
Type helpers can be chained and combined to create sophisticated type transformations:
480
481
```typescript
482
class BaseUser {
483
@ApiProperty({ description: 'User ID' })
484
id: string;
485
486
@ApiProperty({ description: 'User name' })
487
@IsString()
488
name: string;
489
490
@ApiProperty({ description: 'User email' })
491
@IsEmail()
492
email: string;
493
494
@ApiProperty({ description: 'Password' })
495
@IsString()
496
@MinLength(8)
497
password: string;
498
499
@ApiProperty({ description: 'User role' })
500
role: string;
501
502
@ApiProperty({ description: 'Created date' })
503
createdAt: Date;
504
505
@ApiProperty({ description: 'Updated date' })
506
updatedAt: Date;
507
}
508
509
class UserMetadata {
510
@ApiProperty({ description: 'Last login date', required: false })
511
lastLoginAt?: Date;
512
513
@ApiProperty({ description: 'Login count' })
514
@IsNumber()
515
loginCount: number;
516
517
@ApiProperty({ description: 'Account verified' })
518
@IsBoolean()
519
isVerified: boolean;
520
}
521
522
// Complex type transformations
523
class CreateUserDto extends OmitType(BaseUser, ['id', 'createdAt', 'updatedAt'] as const) {}
524
525
class UpdateUserDto extends PartialType(
526
OmitType(BaseUser, ['id', 'password', 'createdAt', 'updatedAt'] as const)
527
) {}
528
529
class UserWithMetadataDto extends IntersectionType(
530
OmitType(BaseUser, ['password'] as const),
531
UserMetadata
532
) {}
533
534
class PublicUserSummaryDto extends PickType(
535
IntersectionType(BaseUser, UserMetadata),
536
['id', 'name', 'isVerified', 'lastLoginAt'] as const
537
) {}
538
539
// Usage in controllers
540
@Controller('users')
541
export class UsersController {
542
@Post()
543
@ApiBody({ type: CreateUserDto })
544
@ApiResponse({ status: 201, type: UserWithMetadataDto })
545
create(@Body() createUserDto: CreateUserDto) {}
546
547
@Patch(':id')
548
@ApiBody({ type: UpdateUserDto })
549
@ApiResponse({ status: 200, type: UserWithMetadataDto })
550
update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {}
551
552
@Get()
553
@ApiResponse({ status: 200, type: [PublicUserSummaryDto] })
554
findAll() {}
555
}
556
```
557
558
## Best Practices
559
560
### 1. Use Explicit Type Assertions
561
```typescript
562
// Good: Explicit 'as const' for type safety
563
class UpdateDto extends PartialType(
564
PickType(BaseDto, ['name', 'email'] as const)
565
) {}
566
567
// Bad: Missing 'as const' may cause type issues
568
class UpdateDto extends PartialType(
569
PickType(BaseDto, ['name', 'email'])
570
) {}
571
```
572
573
### 2. Layer Type Transformations Logically
574
```typescript
575
// Good: Clear, logical progression
576
class CreateUserDto extends OmitType(User, ['id', 'timestamps'] as const) {}
577
class UpdateUserDto extends PartialType(CreateUserDto) {}
578
579
// Better: More explicit about what's omitted at each step
580
class BaseUserData extends PickType(User, ['name', 'email', 'role'] as const) {}
581
class CreateUserDto extends BaseUserData {}
582
class UpdateUserDto extends PartialType(BaseUserData) {}
583
```
584
585
### 3. Create Reusable Base Types
586
```typescript
587
// Create common base types for reuse
588
class BaseEntityDto {
589
@ApiProperty({ description: 'Unique identifier' })
590
id: string;
591
592
@ApiProperty({ description: 'Creation timestamp' })
593
createdAt: Date;
594
595
@ApiProperty({ description: 'Last update timestamp' })
596
updatedAt: Date;
597
}
598
599
class AuditableDto extends BaseEntityDto {
600
@ApiProperty({ description: 'Created by user ID' })
601
createdBy: string;
602
603
@ApiProperty({ description: 'Updated by user ID' })
604
updatedBy: string;
605
}
606
607
// Use in specific DTOs
608
class ProductDto extends IntersectionType(AuditableDto, ProductDataDto) {}
609
class OrderDto extends IntersectionType(AuditableDto, OrderDataDto) {}
610
```
611
612
### 4. Document Complex Type Transformations
613
```typescript
614
/**
615
* User registration DTO
616
* - Excludes system-managed fields (id, timestamps)
617
* - Includes all user-provided fields
618
* - Adds terms acceptance requirement
619
*/
620
class RegisterUserDto extends IntersectionType(
621
OmitType(BaseUser, ['id', 'createdAt', 'updatedAt'] as const),
622
class {
623
@ApiProperty({ description: 'Terms and conditions acceptance' })
624
@IsBoolean()
625
acceptTerms: boolean;
626
}
627
) {}
628
```
629
630
These type helpers provide a powerful, type-safe way to create derived DTOs while maintaining full OpenAPI documentation and validation metadata, enabling you to build maintainable and well-documented APIs with minimal code duplication.