class-transformer — Shape Your Output

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.

4 min read Level 3/5 #nestjs#transformer
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 *View class and convert at the boundary.
  • Coercing a primitive from raw JSON? @Type (in DTOs) for nested classes, @Transform for 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 →