Skip to Content

Transformers

Transformers allow you to modify request and response data during the CRUD operation lifecycle. They are useful for data enrichment, format conversion, and business logic that doesn’t belong in entities.

Request Transformers

Transform incoming request data before processing:

post.controller.ts
import { ApiController, EApiRouteType, EApiDtoType, EApiControllerRequestTransformerType, TRANSFORMER_VALUE_DTO_CONSTANT } from "@elsikora/nestjs-crud-automator"; @ApiController<PostEntity>({ entity: PostEntity, routes: { [EApiRouteType.CREATE]: { request: { transformers: { [EApiDtoType.BODY]: [ { key: "author", type: EApiControllerRequestTransformerType.DYNAMIC, value: TRANSFORMER_VALUE_DTO_CONSTANT.AUTHORIZED_ENTITY, shouldSetValueEvenIfMissing: true, }, { key: "status", type: EApiControllerRequestTransformerType.STATIC, value: "draft", }, ], }, }, }, }, })

Transformer Types

Static Transformers

Set a fixed string value:

{ key: "status", type: EApiControllerRequestTransformerType.STATIC, value: "draft", }

Dynamic Transformers

Extract values from request context:

// Set from authenticated user (user object) { key: "createdBy", type: EApiControllerRequestTransformerType.DYNAMIC, value: TRANSFORMER_VALUE_DTO_CONSTANT.AUTHORIZED_ENTITY, shouldSetValueEvenIfMissing: true, } // Set from request headers { key: "requestSignature", type: EApiControllerRequestTransformerType.DYNAMIC, value: TRANSFORMER_VALUE_DTO_CONSTANT.REQUEST_SIGNATURE, } // Set from request IP { key: "ipAddress", type: EApiControllerRequestTransformerType.DYNAMIC, value: TRANSFORMER_VALUE_DTO_CONSTANT.REQUEST_IP, } // Set from request user-agent { key: "userAgent", type: EApiControllerRequestTransformerType.DYNAMIC, value: TRANSFORMER_VALUE_DTO_CONSTANT.REQUEST_USER_AGENT, }

Available Dynamic Values

Built-in dynamic transformer values:

// Authenticated user entity TRANSFORMER_VALUE_DTO_CONSTANT.AUTHORIZED_ENTITY; // Returns the full user object, not just an id // Request IP address TRANSFORMER_VALUE_DTO_CONSTANT.REQUEST_IP; // Request signature header TRANSFORMER_VALUE_DTO_CONSTANT.REQUEST_SIGNATURE; // Request timestamp header TRANSFORMER_VALUE_DTO_CONSTANT.REQUEST_TIMESTAMP; // Request user-agent header TRANSFORMER_VALUE_DTO_CONSTANT.REQUEST_USER_AGENT;

Response Transformers

Transform outgoing response data:

routes: { [EApiRouteType.GET]: { response: { transformers: [ { key: "viewer", type: EApiControllerRequestTransformerType.DYNAMIC, value: TRANSFORMER_VALUE_DTO_CONSTANT.AUTHORIZED_ENTITY, shouldSetValueEvenIfMissing: true, }, { key: "responseSource", type: EApiControllerRequestTransformerType.STATIC, value: "api", shouldSetValueEvenIfMissing: true, }, ], }, }, }

Transform Based on User Role

For role-based masking or computed fields, use subscribers instead of request transformers:

@Injectable() @ApiRouteSubscriber({ entity: UserEntity }) export class UserMaskSubscriber extends ApiRouteSubscriberBase<UserEntity> { async onAfterGet(context: TApiSubscriberRouteAfterGetContext<UserEntity>): Promise<UserEntity | undefined> { const user = context.DATA.authenticationRequest?.user; if (user?.role !== "admin") { context.result.sensitiveData = null; } return context.result; } }

Complex Transformations

Request transformers are intentionally limited to static strings and built-in dynamic values (authorized user, IP, signature, timestamp, user-agent). For computed fields, conditional logic, nested/array mapping, or async lookups, use subscribers or class-transformer DTOs (see below).

Using class-transformer

Leverage class-transformer for advanced transformations:

user.entity.ts
import { Transform, Exclude } from "class-transformer"; @Entity("users") export class UserEntity { @Column() @Transform(({ value }) => value.toLowerCase()) email: string; @Column() @Exclude() // Never include in responses password: string; @Column() @Transform(({ value }) => value.trim()) username: string; @Column({ type: "jsonb" }) @Transform(({ value }) => JSON.stringify(value)) metadata: Record<string, any>; }

Pre-Processing Before Validation

Use function subscribers to normalize incoming data:

@Injectable() @ApiFunctionSubscriber({ entity: UserEntity }) export class UserNormalizeSubscriber extends ApiFunctionSubscriberBase<UserEntity> { async onBeforeCreate(context: TApiSubscriberFunctionBeforeCreateContext<UserEntity>): Promise<TApiFunctionCreateProperties<UserEntity>> { const { body } = context.result; if (body.email) { body.email = body.email.toLowerCase().trim(); } if (Array.isArray(body.tags)) { body.tags = body.tags.map((tag) => tag.toLowerCase()); } return context.result; } }

Async Transformers

Async operations should be handled in subscribers instead of request transformers.

Removing Properties

Remove properties from response using class-transformer (@Exclude) or subscriber hooks.

Global Transformers

Apply transformers to all routes:

@ApiController<UserEntity>({ entity: UserEntity, globalTransformers: { request: { [EApiDtoType.BODY]: [ { key: "source", type: EApiControllerRequestTransformerType.STATIC, value: "api", }, ], }, response: [ { key: "responseSource", type: EApiControllerRequestTransformerType.STATIC, value: "api", shouldSetValueEvenIfMissing: true, }, ], }, routes: { // ... route configurations }, })

Transformer Order

Transformers execute in the following order:

  1. Global request transformers
  2. Route-specific request transformers
  3. Validation
  4. Service method execution
  5. Route-specific response transformers
  6. Global response transformers

Performance Considerations

Avoid Heavy Operations

// Prefer relations or subscribers for heavy work request: { relations: { shouldLoadRelations: true, relationsToLoad: ["posts"], }, }

Cache Expensive Computations

Use subscribers with caching if you need expensive computed fields.

Next Steps

Last updated on