CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-nestjs--swagger

OpenAPI (Swagger) module for Nest framework enabling automatic API documentation generation from TypeScript decorators

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

type-helpers.mddocs/

Type Helpers

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.

Overview

Type helpers extend the functionality from @nestjs/mapped-types with full OpenAPI/Swagger integration, ensuring that derived types automatically inherit proper API documentation metadata.

import { 
  PartialType, 
  PickType, 
  OmitType, 
  IntersectionType 
} from '@nestjs/swagger';

All type helpers work by:

  1. Creating a new class that extends or modifies the original class structure
  2. Inheriting validation metadata from class-validator decorators
  3. Inheriting transformation metadata from class-transformer decorators
  4. Copying and adapting OpenAPI metadata for proper schema generation
  5. Maintaining proper TypeScript type inference

PartialType

PartialType { .api }

function PartialType<T>(
  classRef: Type<T>,
  options?: {
    skipNullProperties?: boolean;
  }
): Type<Partial<T>>

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.

Parameters:

  • classRef: The base class to make partial
  • options.skipNullProperties: If false, validations ignore only undefined values. If true (default), validations ignore both null and undefined values.

Type Behavior:

  • All properties become optional (prop?: type)
  • All OpenAPI properties get required: false
  • Validation decorators are modified to allow undefined/null values
  • Original property types and constraints are preserved

Basic Usage:

import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNumber, IsEmail } from 'class-validator';

class CreateUserDto {
  @ApiProperty({ description: 'User name' })
  @IsString()
  name: string;

  @ApiProperty({ description: 'User email' })
  @IsEmail()
  email: string;

  @ApiProperty({ description: 'User age' })
  @IsNumber()
  age: number;
}

// All properties become optional
class UpdateUserDto extends PartialType(CreateUserDto) {}

// UpdateUserDto is equivalent to:
class UpdateUserDto {
  @ApiProperty({ description: 'User name', required: false })
  @IsOptional()
  @IsString()
  name?: string;

  @ApiProperty({ description: 'User email', required: false })
  @IsOptional()
  @IsEmail()
  email?: string;

  @ApiProperty({ description: 'User age', required: false })
  @IsOptional()
  @IsNumber()
  age?: number;
}

Advanced Usage with Options:

// Default behavior: ignore null and undefined
class UpdateUserDto extends PartialType(CreateUserDto) {}

// Custom behavior: only ignore undefined (null values are validated)
class StrictUpdateUserDto extends PartialType(CreateUserDto, {
  skipNullProperties: false
}) {}

// Usage in controller
@Controller('users')
export class UsersController {
  @Patch(':id')
  @ApiOperation({ summary: 'Update user' })
  @ApiResponse({ status: 200, type: User })
  update(
    @Param('id') id: string, 
    @Body() updateUserDto: UpdateUserDto
  ) {
    // Only provided fields will be updated
    return this.usersService.update(id, updateUserDto);
  }
}

PickType

PickType { .api }

function PickType<T, K extends keyof T>(
  classRef: Type<T>,
  keys: readonly K[]
): Type<Pick<T, (typeof keys)[number]>>

Creates a new type by selecting (picking) specific properties from the input type. Useful for creating focused DTOs that only contain relevant fields.

Parameters:

  • classRef: The base class to pick properties from
  • keys: Array of property names to include in the new type

Type Behavior:

  • Only specified properties are included
  • All metadata (validation, transformation, OpenAPI) is preserved for picked properties
  • Properties maintain their original required/optional status
  • TypeScript enforces that keys exist on the original type

Basic Usage:

class User {
  @ApiProperty({ description: 'Unique user ID' })
  id: string;

  @ApiProperty({ description: 'User name' })
  @IsString()
  name: string;

  @ApiProperty({ description: 'User email' })
  @IsEmail()
  email: string;

  @ApiProperty({ description: 'User password' })
  @IsString()
  @MinLength(8)
  password: string;

  @ApiProperty({ description: 'Creation date' })
  createdAt: Date;

  @ApiProperty({ description: 'Last update date' })
  updatedAt: Date;
}

// Pick only public profile information
class UserProfileDto extends PickType(User, ['id', 'name', 'email'] as const) {}

// UserProfileDto is equivalent to:
class UserProfileDto {
  @ApiProperty({ description: 'Unique user ID' })
  id: string;

  @ApiProperty({ description: 'User name' })
  @IsString()
  name: string;

  @ApiProperty({ description: 'User email' })
  @IsEmail()
  email: string;
}

Practical Examples:

// Login credentials (pick only auth-related fields)
class LoginDto extends PickType(User, ['email', 'password'] as const) {}

// User summary for lists (pick minimal fields)
class UserSummaryDto extends PickType(User, ['id', 'name'] as const) {}

// Admin view (pick all except sensitive data)
class AdminUserDto extends PickType(User, [
  'id', 'name', 'email', 'createdAt', 'updatedAt'
] as const) {}

@Controller('users')
export class UsersController {
  @Get('profile')
  @ApiResponse({ status: 200, type: UserProfileDto })
  getProfile(): UserProfileDto {
    // Return only public profile data
  }

  @Post('login')
  @ApiBody({ type: LoginDto })
  @ApiResponse({ status: 200, description: 'Login successful' })
  login(@Body() loginDto: LoginDto) {
    // Only email and password are accepted
  }
}

OmitType

OmitType { .api }

function OmitType<T, K extends keyof T>(
  classRef: Type<T>,
  keys: readonly K[]
): Type<Omit<T, (typeof keys)[number]>>

Creates a new type by excluding (omitting) specific properties from the input type. Useful for removing sensitive or computed fields from DTOs.

Parameters:

  • classRef: The base class to omit properties from
  • keys: Array of property names to exclude from the new type

Type Behavior:

  • Specified properties are excluded from the new type
  • All other properties maintain their metadata and constraints
  • TypeScript ensures omitted keys exist on the original type
  • Remaining properties keep their original required/optional status

Basic Usage:

class User {
  @ApiProperty({ description: 'Unique user ID' })
  id: string;

  @ApiProperty({ description: 'User name' })
  @IsString()
  name: string;

  @ApiProperty({ description: 'User email' })
  @IsEmail()
  email: string;

  @ApiProperty({ description: 'User password' })
  @IsString()
  @MinLength(8)
  password: string;

  @ApiProperty({ description: 'Internal user role' })
  role: string;
}

// Create user DTO without ID (auto-generated) and role (set by system)
class CreateUserDto extends OmitType(User, ['id', 'role'] as const) {}

// CreateUserDto is equivalent to:
class CreateUserDto {
  @ApiProperty({ description: 'User name' })
  @IsString()
  name: string;

  @ApiProperty({ description: 'User email' })
  @IsEmail()
  email: string;

  @ApiProperty({ description: 'User password' })
  @IsString()
  @MinLength(8)
  password: string;
}

Practical Examples:

// Response DTO without sensitive information
class UserResponseDto extends OmitType(User, ['password'] as const) {}

// Public API DTO without internal fields
class PublicUserDto extends OmitType(User, ['password', 'role'] as const) {}

// Registration DTO without system-managed fields
class RegisterDto extends OmitType(User, ['id', 'role', 'createdAt', 'updatedAt'] as const) {}

@Controller('users')
export class UsersController {
  @Post('register')
  @ApiBody({ type: RegisterDto })
  @ApiResponse({ status: 201, type: UserResponseDto })
  register(@Body() registerDto: RegisterDto): UserResponseDto {
    // Password and role are excluded from response
    const user = this.usersService.create(registerDto);
    return omit(user, ['password']);
  }

  @Get(':id')
  @ApiResponse({ status: 200, type: PublicUserDto })
  findOne(@Param('id') id: string): PublicUserDto {
    // Internal fields are automatically excluded
    return this.usersService.findPublicInfo(id);
  }
}

IntersectionType

IntersectionType { .api }

function IntersectionType<T extends Type[]>(
  ...classRefs: T
): Intersection<T>

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.

Parameters:

  • ...classRefs: Variable number of classes to combine

Type Behavior:

  • All properties from all input classes are merged into the new type
  • If properties have the same name, the last class in the parameter list takes precedence
  • All validation and transformation metadata is inherited
  • OpenAPI metadata is merged appropriately

Type Definitions:

type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;

type ClassRefsToConstructors<T extends Type[]> = {
  [U in keyof T]: T[U] extends Type<infer V> ? V : never;
};

type Intersection<T extends Type[]> = Type<UnionToIntersection<ClassRefsToConstructors<T>[number]>>;

Basic Usage:

class BaseDto {
  @ApiProperty({ description: 'Record ID' })
  @IsString()
  id: string;

  @ApiProperty({ description: 'Creation timestamp' })
  createdAt: Date;
}

class UserInfoDto {
  @ApiProperty({ description: 'User name' })
  @IsString()
  @MinLength(2)
  name: string;

  @ApiProperty({ description: 'User email' })
  @IsEmail()
  email: string;
}

class ContactDto {
  @ApiProperty({ description: 'Phone number', required: false })
  @IsOptional()
  @IsPhoneNumber()
  phone?: string;

  @ApiProperty({ description: 'Address', required: false })
  @IsOptional()
  @IsString()
  address?: string;
}

// Combine all three classes into a complete user DTO
class CompleteUserDto extends IntersectionType(BaseDto, UserInfoDto, ContactDto) {}

// CompleteUserDto contains all properties:
class CompleteUserDto {
  @ApiProperty({ description: 'Record ID' })
  @IsString()
  id: string;

  @ApiProperty({ description: 'Creation timestamp' })
  createdAt: Date;

  @ApiProperty({ description: 'User name' })
  @IsString()
  @MinLength(2)
  name: string;

  @ApiProperty({ description: 'User email' })
  @IsEmail()
  email: string;

  @ApiProperty({ description: 'Phone number', required: false })
  @IsOptional()
  @IsPhoneNumber()
  phone?: string;

  @ApiProperty({ description: 'Address', required: false })
  @IsOptional()
  @IsString()
  address?: string;
}

Complex Composition Examples:

// Base classes for different aspects
class TimestampDto {
  @ApiProperty({ description: 'Creation date' })
  createdAt: Date;

  @ApiProperty({ description: 'Last update date' })
  updatedAt: Date;
}

class OwnershipDto {
  @ApiProperty({ description: 'Creator user ID' })
  @IsString()
  createdBy: string;

  @ApiProperty({ description: 'Last modifier user ID' })
  @IsString()
  updatedBy: string;
}

class StatusDto {
  @ApiProperty({ 
    description: 'Record status',
    enum: ['active', 'inactive', 'pending', 'archived']
  })
  @IsEnum(['active', 'inactive', 'pending', 'archived'])
  status: string;
}

// Product-specific information
class ProductDataDto {
  @ApiProperty({ description: 'Product name' })
  @IsString()
  @MinLength(1)
  name: string;

  @ApiProperty({ description: 'Product description' })
  @IsString()
  description: string;

  @ApiProperty({ description: 'Product price' })
  @IsNumber()
  @Min(0)
  price: number;
}

// Complete product DTO with all metadata
class ProductDto extends IntersectionType(
  ProductDataDto,
  TimestampDto,
  OwnershipDto,
  StatusDto
) {}

// Create DTO that omits system-managed fields
class CreateProductDto extends IntersectionType(
  ProductDataDto,
  PickType(StatusDto, ['status'] as const)
) {}

// Update DTO with partial product data and ownership
class UpdateProductDto extends IntersectionType(
  PartialType(ProductDataDto),
  OwnershipDto
) {}

Combining Type Helpers

Type helpers can be chained and combined to create sophisticated type transformations:

class BaseUser {
  @ApiProperty({ description: 'User ID' })
  id: string;

  @ApiProperty({ description: 'User name' })
  @IsString()
  name: string;

  @ApiProperty({ description: 'User email' })
  @IsEmail()
  email: string;

  @ApiProperty({ description: 'Password' })
  @IsString()
  @MinLength(8)
  password: string;

  @ApiProperty({ description: 'User role' })
  role: string;

  @ApiProperty({ description: 'Created date' })
  createdAt: Date;

  @ApiProperty({ description: 'Updated date' })
  updatedAt: Date;
}

class UserMetadata {
  @ApiProperty({ description: 'Last login date', required: false })
  lastLoginAt?: Date;

  @ApiProperty({ description: 'Login count' })
  @IsNumber()
  loginCount: number;

  @ApiProperty({ description: 'Account verified' })
  @IsBoolean()
  isVerified: boolean;
}

// Complex type transformations
class CreateUserDto extends OmitType(BaseUser, ['id', 'createdAt', 'updatedAt'] as const) {}

class UpdateUserDto extends PartialType(
  OmitType(BaseUser, ['id', 'password', 'createdAt', 'updatedAt'] as const)
) {}

class UserWithMetadataDto extends IntersectionType(
  OmitType(BaseUser, ['password'] as const),
  UserMetadata
) {}

class PublicUserSummaryDto extends PickType(
  IntersectionType(BaseUser, UserMetadata),
  ['id', 'name', 'isVerified', 'lastLoginAt'] as const
) {}

// Usage in controllers
@Controller('users')
export class UsersController {
  @Post()
  @ApiBody({ type: CreateUserDto })
  @ApiResponse({ status: 201, type: UserWithMetadataDto })
  create(@Body() createUserDto: CreateUserDto) {}

  @Patch(':id')
  @ApiBody({ type: UpdateUserDto })
  @ApiResponse({ status: 200, type: UserWithMetadataDto })
  update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {}

  @Get()
  @ApiResponse({ status: 200, type: [PublicUserSummaryDto] })
  findAll() {}
}

Best Practices

1. Use Explicit Type Assertions

// Good: Explicit 'as const' for type safety
class UpdateDto extends PartialType(
  PickType(BaseDto, ['name', 'email'] as const)
) {}

// Bad: Missing 'as const' may cause type issues
class UpdateDto extends PartialType(
  PickType(BaseDto, ['name', 'email'])
) {}

2. Layer Type Transformations Logically

// Good: Clear, logical progression
class CreateUserDto extends OmitType(User, ['id', 'timestamps'] as const) {}
class UpdateUserDto extends PartialType(CreateUserDto) {}

// Better: More explicit about what's omitted at each step
class BaseUserData extends PickType(User, ['name', 'email', 'role'] as const) {}
class CreateUserDto extends BaseUserData {}
class UpdateUserDto extends PartialType(BaseUserData) {}

3. Create Reusable Base Types

// Create common base types for reuse
class BaseEntityDto {
  @ApiProperty({ description: 'Unique identifier' })
  id: string;

  @ApiProperty({ description: 'Creation timestamp' })
  createdAt: Date;

  @ApiProperty({ description: 'Last update timestamp' })
  updatedAt: Date;
}

class AuditableDto extends BaseEntityDto {
  @ApiProperty({ description: 'Created by user ID' })
  createdBy: string;

  @ApiProperty({ description: 'Updated by user ID' })
  updatedBy: string;
}

// Use in specific DTOs
class ProductDto extends IntersectionType(AuditableDto, ProductDataDto) {}
class OrderDto extends IntersectionType(AuditableDto, OrderDataDto) {}

4. Document Complex Type Transformations

/**
 * User registration DTO
 * - Excludes system-managed fields (id, timestamps)
 * - Includes all user-provided fields
 * - Adds terms acceptance requirement
 */
class RegisterUserDto extends IntersectionType(
  OmitType(BaseUser, ['id', 'createdAt', 'updatedAt'] as const),
  class {
    @ApiProperty({ description: 'Terms and conditions acceptance' })
    @IsBoolean()
    acceptTerms: boolean;
  }
) {}

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.

docs

decorators.md

document-builder.md

index.md

interfaces.md

swagger-module.md

type-helpers.md

tile.json