Hide Secrets, Rename Fields, Coerce Types
class-transformer — Shape Your Output
class-transformer turns plain objects into class instances and back. Pair it with ClassSerializerInterceptor to control exactly what your API returns.
What you'll learn
- Use @Exclude and @Expose to control serialization
- Call plainToInstance and instanceToPlain manually when needed
- Wire ClassSerializerInterceptor globally for automatic output shaping
class-validator polices what comes in. class-transformer shapes what goes out — and in. With a few decorators on an entity class, you can hide sensitive fields, rename them, or coerce types on the way out the door.
Hiding a Field With @Exclude
The classic case: a User entity that has a password field you must
never return.
import { Exclude } from 'class-transformer';
export class User {
id: string;
email: string;
@Exclude()
password: string;
} By itself, that decorator does nothing — class-transformer runs only when
you tell it to. Nest’s ClassSerializerInterceptor runs it automatically
on the value returned from each handler.
import { ClassSerializerInterceptor } from '@nestjs/common';
// main.ts
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector))); Now GET /users/:id returns the user without the password field — even
though your service handed back the full entity. Critically, the handler
must return a class instance (not a plain object) for the interceptor to
work.
Manual Conversion
When you need full control — say, in a script or job that bypasses HTTP — call the helpers directly.
import { plainToInstance, instanceToPlain } from 'class-transformer';
const raw = { id: '1', email: 'a@b.com', password: 'secret' };
const user = plainToInstance(User, raw); // raw → User instance
const safe = instanceToPlain(user); // User → plain (Exclude applied)
// safe = { id: '1', email: 'a@b.com' } Renaming With @Expose
@Expose({ name: 'snake_case_name' }) renames a field on serialization.
Combine with the right excludeExtraneousValues option on the interceptor
to enforce an opt-in model — only @Exposed fields are returned.
import { Expose } from 'class-transformer';
export class UserView {
@Expose() id: string;
@Expose({ name: 'email_address' }) email: string;
// password isn't @Expose'd, so it never leaks
} Used together with new ClassSerializerInterceptor(reflector, { strategy: 'excludeAll' }), this gives you allow-listed output — much safer
for entities that gain fields over time.
@Transform — Custom Coercion
@Transform runs a function on a field during conversion. Use it to format
dates, parse JSON columns, or trim strings.
import { Transform } from 'class-transformer';
export class CommentDto {
@Transform(({ value }) => value?.trim())
body: string;
@Transform(({ value }) => new Date(value).toISOString())
createdAt: Date;
} When to Use What
- Hiding a secret field?
@Exclude+ClassSerializerInterceptor. - Returning a different shape than your entity? Define a separate
*Viewclass and convert at the boundary. - Coercing a primitive from raw JSON?
@Type(in DTOs) for nested classes,@Transformfor everything else.
Knowing this layer means you’ll rarely have to write hand-coded mapping code between database entities and API responses.
Guards — Allow or Deny →