OpenAPI (Swagger) module for Nest framework enabling automatic API documentation generation from TypeScript decorators
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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.
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:
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 partialoptions.skipNullProperties: If false, validations ignore only undefined values. If true (default), validations ignore both null and undefined values.Type Behavior:
prop?: type)required: falseBasic 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);
}
}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 fromkeys: Array of property names to include in the new typeType Behavior:
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
}
}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 fromkeys: Array of property names to exclude from the new typeType Behavior:
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);
}
}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 combineType Behavior:
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
) {}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() {}
}// 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'])
) {}// 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) {}// 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) {}/**
* 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.