Overview
Serialization allows you to transform entity data before sending responses. Use custom DTOs (Data Transfer Objects) to:
- Hide sensitive fields
- Transform data formats
- Add computed properties
- Control response structure for different operations
Basic Usage
Define a response DTO and apply it to your controller:
get-company-response.dto.ts
import { ApiProperty } from '@nestjs/swagger';
import { Exclude } from 'class-transformer';
export class GetCompanyResponseDto {
@ApiProperty({ type: 'number' })
id: string;
@ApiProperty({ type: 'string' })
name: string;
@ApiProperty({ type: 'string' })
domain: string;
@Exclude()
createdAt: any;
@Exclude()
updatedAt: any;
}
import { SerializeOptions } from '@nestjsx/crud';
import { GetCompanyResponseDto } from './get-company-response.dto';
export const serialize: SerializeOptions = {
get: GetCompanyResponseDto,
};
import { Controller } from '@nestjs/common';
import { Crud } from '@nestjsx/crud';
import { Company } from './company.entity';
import { CompaniesService } from './companies.service';
import { serialize } from './responses';
@Crud({
model: { type: Company },
serialize,
})
@Controller('companies')
export class CompaniesController {
constructor(public service: CompaniesService) {}
}
The @Exclude() decorator from class-transformer hides fields from the response.
Serialization Options
You can define different DTOs for each operation:
import { SerializeOptions } from '@nestjsx/crud';
import { GetNoteResponseDto } from './get-note-response.dto';
import { CreateNoteResponseDto } from './create-note-response.dto';
import { UpdateNoteResponseDto } from './update-note-response.dto';
import { DeleteNoteResponseDto } from './delete-note-response.dto';
export const serialize: SerializeOptions = {
get: GetNoteResponseDto, // For getOne
getMany: GetNoteResponseDto, // For getMany (uses same DTO)
create: CreateNoteResponseDto, // For createOne
createMany: CreateNoteResponseDto, // For createMany
update: UpdateNoteResponseDto, // For updateOne
replace: UpdateNoteResponseDto, // For replaceOne
delete: DeleteNoteResponseDto, // For deleteOne
recover: GetNoteResponseDto, // For recoverOne
};
Available Options
| Property | Type | Description |
|---|
get | Type<any> | false | DTO for getOne operation |
getMany | Type<any> | false | DTO for getMany operation |
create | Type<any> | false | DTO for createOne operation |
createMany | Type<any> | false | DTO for createMany operation |
update | Type<any> | false | DTO for updateOne operation |
replace | Type<any> | false | DTO for replaceOne operation |
delete | Type<any> | false | DTO for deleteOne operation |
recover | Type<any> | false | DTO for recoverOne operation |
Set any option to false to disable serialization for that operation and return the raw entity.
Response DTOs
Simple Response DTO
Control which fields are returned:
import { ApiProperty } from '@nestjs/swagger';
import { IsNumber } from 'class-validator';
export class GetNoteResponseDto {
@ApiProperty({ type: 'number' })
@IsNumber()
id: string;
@ApiProperty({ type: 'number' })
@IsNumber()
revisionId: string;
// Other fields from the entity are automatically excluded
}
Delete Response DTO
Customize delete responses:
delete-device-response.dto.ts
import { ApiProperty } from '@nestjs/swagger';
import { Exclude } from 'class-transformer';
export class DeleteDeviceResponseDto {
@ApiProperty({ type: 'string' })
deviceKey: string;
@Exclude()
description?: string;
}
Use with the returnDeleted option:
@Crud({
model: { type: Device },
serialize: {
delete: DeleteDeviceResponseDto,
},
routes: {
deleteOneBase: {
returnDeleted: true,
},
},
})
@Controller('devices')
export class DevicesController {
constructor(public service: DevicesService) {}
}
Leverage class-transformer decorators for advanced transformations:
Exclude Fields
import { Exclude } from 'class-transformer';
export class UserResponseDto {
id: number;
email: string;
@Exclude()
password: string;
@Exclude()
refreshToken: string;
}
Expose Fields
import { Expose } from 'class-transformer';
export class UserResponseDto {
@Expose()
id: number;
@Expose()
email: string;
// All other fields are excluded
}
import { Transform } from 'class-transformer';
export class ProductResponseDto {
id: number;
@Transform(({ value }) => value.toUpperCase())
name: string;
@Transform(({ value }) => parseFloat(value).toFixed(2))
price: number;
}
Conditional Serialization
import { Exclude, Expose } from 'class-transformer';
export class UserResponseDto {
id: number;
email: string;
@Expose({ groups: ['admin'] })
role: string;
@Exclude({ toPlainOnly: true })
password: string;
}
Groups
Use groups with @CrudAuth() for role-based serialization:
import { Expose } from 'class-transformer';
export class UserResponseDto {
@Expose()
id: number;
@Expose()
email: string;
@Expose({ groups: ['admin'] })
internalNotes: string;
@Expose({ groups: ['admin', 'manager'] })
salary: number;
}
import { Crud, CrudAuth } from '@nestjsx/crud';
@Crud({
model: { type: User },
serialize: {
get: UserResponseDto,
},
})
@CrudAuth({
groups: (req) => {
if (req.user.role === 'admin') return ['admin'];
if (req.user.role === 'manager') return ['manager'];
return [];
},
})
@Controller('users')
export class UsersController {
constructor(public service: UsersService) {}
}
Disabling Serialization
Disable serialization for specific operations:
@Crud({
model: { type: User },
serialize: {
get: false, // Return raw entity for getOne
getMany: false, // Return raw entities for getMany
update: UserResponseDto, // Still serialize updates
},
})
Global Serialization Settings
Disable serialization globally:
import { CrudConfigService } from '@nestjsx/crud';
CrudConfigService.load({
serialize: {
get: false,
getMany: false,
// Other operations...
},
});
Disabling serialization globally affects all controllers unless overridden.
Nested Objects
Handle nested entities in responses:
import { Type } from 'class-transformer';
class CompanyDto {
id: number;
name: string;
}
export class UserResponseDto {
id: number;
email: string;
@Type(() => CompanyDto)
company: CompanyDto;
}
Complete Example
Here’s a full serialization setup:
responses/get-user-response.dto.ts
import { ApiProperty } from '@nestjs/swagger';
import { Exclude, Expose, Transform, Type } from 'class-transformer';
class ProfileDto {
@ApiProperty()
firstName: string;
@ApiProperty()
lastName: string;
@Expose()
@Transform(({ obj }) => `${obj.firstName} ${obj.lastName}`)
fullName: string;
}
export class GetUserResponseDto {
@ApiProperty({ type: 'number' })
id: number;
@ApiProperty({ type: 'string' })
email: string;
@ApiProperty({ type: () => ProfileDto })
@Type(() => ProfileDto)
profile: ProfileDto;
@Exclude()
password: string;
@Exclude()
createdAt: Date;
@Exclude()
updatedAt: Date;
}
import { SerializeOptions } from '@nestjsx/crud';
import { GetUserResponseDto } from './get-user-response.dto';
export const serialize: SerializeOptions = {
get: GetUserResponseDto,
getMany: GetUserResponseDto,
};
import { Controller } from '@nestjs/common';
import { Crud } from '@nestjsx/crud';
import { User } from './user.entity';
import { UsersService } from './users.service';
import { serialize } from './responses';
@Crud({
model: { type: User },
serialize,
query: {
join: {
profile: { eager: true },
},
},
})
@Controller('users')
export class UsersController {
constructor(public service: UsersService) {}
}
Best Practices
Always Hide Sensitive Data
Use @Exclude() for passwords, tokens, and other sensitive information.
Use Swagger Decorators
Add @ApiProperty() decorators to response DTOs for accurate API documentation.
Reuse DTOs
Use the same DTO for similar operations (e.g., get and getMany) to maintain consistency.
Validate DTOs
Even though these are response DTOs, adding validation decorators helps with type safety.
- Authentication - Use groups for role-based serialization
- Swagger - Document your response DTOs
- DTOs - Learn about request DTOs