or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

decorators.mddocument-builder.mdindex.mdinterfaces.mdswagger-module.mdtype-helpers.md

type-helpers.mddocs/

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.