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:
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:
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:
- Global request transformers
- Route-specific request transformers
- Validation
- Service method execution
- Route-specific response transformers
- 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
- Validation - Validate transformed data
- Subscriber System - Alternative approach for data transformation
- Core Concepts - Controllers - Controller transformer configuration