The FastTransformInterceptor
is a powerful tool for automatically transforming and validating your API responses in NestJS applications. This document covers all the possibilities and features of the transformer.
- Installation
- Configuration
- Basic Usage
- Decorators
- DTO Configuration
- Transformation Behavior
- DTO Property Types and Behaviors
- Error Handling
- Advanced Features
- Best Practices
fast-transform-interceptor is both fast and easy to use. Normally it will return about 5-10 times faster than using class-transformer.
// Using Sequelize model in service
class User extends Model {
@Column
username: string;
@Column
password: string;
}
class UserDto {
username: string;
}
class UserService {
async getUsers(): Promise<User> {
// We return Sequelize objects
return this.userModel.findAll();
}
}
class UserController {
@Get()
@ResponseType({type: UserDto})
async getAll() {
// Still no conversion from the Sequelize objects
return this.userService.getUsers();
}
}
This returns only username in the api result. And if you use the swagger cli plugin for documentation, you will normally get correct types on all the results as in documentation.
Before using the FastTransformInterceptor
, make sure you have the @nestjs/swagger
package installed. You can install it using npm or yarn:
npm install --save @nestjs/swagger fast-transform-interceptor
# or
yarn add @nestjs/swagger fast-transform-interceptor
After installing the @nestjs/swagger
package, you need to configure the Swagger plugin in your nest-cli.json
file. This step is crucial for the FastTransformInterceptor
to work correctly.
-
Open your
nest-cli.json
file (create one in your project root if it doesn't exist). -
Add or modify the
plugins
section to include the Swagger plugin:
{
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"plugins": [
{
"name": "@nestjs/swagger",
"options": {
"classValidatorShim": true,
"introspectComments": true
}
}
]
}
}
This configuration enables the Swagger plugin, which is necessary for the FastTransformInterceptor
to correctly process the DTO metadata.
You do not need to start the swagger documentation for FastTransformInterceptor
to work.
When setting up the FastTransformInterceptor
, you can provide default values for the response structure. These defaults are used when not overridden by the @ResponseType
decorator.
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { FastTransformInterceptor } from './fast-transform.interceptor';
@Module({
providers: [
{
provide: APP_INTERCEPTOR,
useFactory: (reflector: Reflector) => {
return new FastTransformInterceptor({
reflector
});
},
inject: [Reflector]
},
],
})
export class AppModule {}
Or a more feature-rich setup:
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { FastTransformInterceptor } from './fast-transform.interceptor';
@Module({
providers: [
{
provide: APP_INTERCEPTOR,
useFactory: (reflector: Reflector, configService: ConfigService) => {
return new FastTransformInterceptor({
reflector,
debug: configService.get<boolean>("debug"),
defaultResponse: { meta: null, data: null },
defaultResponseKey: "data",
defaultDataKey: null
});
},
inject: [Reflector, ConfigService]
},
],
})
export class AppModule {}
defaultResponse
: The default structure of the response object.defaultResponseKey
: The key where the transformed data will be placed in the response object.defaultDataKey
: The key to look for the data in the controller's response. If null, the entire response is considered as data.
The @ResponseType
decorator allows you to specify the DTO type for the response and override the default configuration on a per-route basis.
@ResponseType({
type: UserDto,
response?: any,
responseKey?: string,
dataKey?: string | null,
noTransform?: boolean,
status?: number,
additionalProperties?: Record<string, any>
})
type
: The DTO class to use for transformation (required)response
: Override the default response structure for this routeresponseKey
: Override the default response key for this routedataKey
: Override the default data key for this routenoTransform
: Skip transformation for this response (default:false
)status
: HTTP status code for the response (default: 200)additionalProperties
: Additional properties to include in the response
- Using global defaults:
@Get()
@ResponseType({ type: UserDto })
getUsers() {
// This will use the global default configuration
}
- Overriding response structure:
@Get()
@ResponseType({
type: UserDto,
response: { success: true, result: null },
responseKey: "result"
})
getUsers() {
// This will override the response structure and key
}
- Specifying a data key:
@Get()
@ResponseType({
type: UserDto,
dataKey: "users"
})
getUsers() {
// This expects the controller to return { users: [...] }
// and will transform the content of the "users" key
}
- Disabling transformation:
@Get()
@ResponseType({
type: UserDto,
noTransform: true
})
getRawUsers() {
// This will skip the transformation process
}
By using these configuration options and the @ResponseType
decorator, you can fine-tune how your API responses are structured and transformed on both a global and per-route basis.
The @NoCheck
decorator can be used on a DTO class or property to skip validation for that class or property.
import { NoCheck } from './decorators/no-check.decorator';
@NoCheck()
export class PartialUserDto {
// Properties here won't be checked
}
export class UserDto {
@NoCheck()
flexibleField: any;
}
The @NoOut
decorator can be used on a DTO property to exclude it from the output.
import { NoOut } from './decorators/no-out.decorator';
export class UserDto {
@NoOut()
password: string;
}
The @NoCheckPrimitives
decorator can be used on a DTO class to skip validation for all primitive type properties within that class. This can be useful for improving performance when type checking is not critical for primitive values.
import { NoCheckPrimitives } from './decorators/no-check-primitives.decorator';
@NoCheckPrimitives()
export class PerformanceOptimizedDto {
id: number;
name: string;
isActive: boolean;
createdAt: Date;
// These primitive properties will not be type-checked during transformation
nestedObject: ComplexDto; // Non-primitive types are still validated and transformed
}
When @NoCheckPrimitives
is applied to a DTO:
- All primitive type properties (number, string, boolean, bigint, symbol, Date) are passed through as-is without type checking.
- Non-primitive types (e.g., nested objects, arrays of objects) are still validated and transformed as usual.
- This decorator affects only the class it's applied to, not any nested DTOs.
- For nested objects and arrays, the
@NoCheckPrimitives
decorator will still be respected for their primitive properties.
Use this decorator with caution, as it bypasses type safety for primitives. It's best used in scenarios where you're confident about the types of incoming data and prioritize performance over strict type checking.
DTOs (Data Transfer Objects) are configured using TypeScript types and decorators. The FastTransformInterceptor
uses this information to validate and transform the response data. Here are various ways to define properties in your DTOs and how they are handled:
export class UserDto {
id: number;
name: string;
isActive: boolean;
createdAt: Date;
bigNumber: bigint;
uniqueSymbol: symbol;
}
These primitive types are validated during transformation. If a value doesn't match its declared type, the transformer will attempt to coerce it to the correct type. If coercion fails, a TransformError
will be thrown.
import { AddressDto } from './address.dto';
export class UserDto {
details: {
age: number;
hobbies: string[];
};
address: AddressDto;
friends: UserDto[];
}
- Nested objects are recursively transformed.
- Arrays of primitive types or complex types are handled accordingly.
enum UserRole {
Admin = 'ADMIN',
User = 'USER',
Guest = 'GUEST'
}
export class UserDto {
role: UserRole;
}
Enum types are validated against their defined values.
export class UserDto {
middleName: string | null;
nickname?: string;
deletedAt?: Date | null;
}
- Nullable types (using
| null
) allow either the specified type or null. - Optional properties (marked with
?
) can be undefined and will be skipped if not present. - Required properties that are undefined will throw a
TransformError
.
import { NoCheck } from './decorators/no-check.decorator';
import { NoOut } from './decorators/no-out.decorator';
export class UserDto {
@NoCheck()
flexibleField: any;
@NoOut()
password: string;
}
@NoCheck()
skips validation for the decorated property.@NoOut()
excludes the decorated property from the output.
import { NoCheckPrimitives } from './decorators/no-check-primitives.decorator';
@NoCheckPrimitives()
export class PerformanceOptimizedDto {
id: number;
name: string;
isActive: boolean;
// Primitive properties won't be type-checked
complexField: ComplexDto; // Still validated and transformed
}
@NoCheckPrimitives()
skips validation for all primitive type properties in the class, improving performance when strict type checking isn't necessary.
Remember, the FastTransformInterceptor
uses these DTO configurations to ensure your API responses are consistent, validated, and well-structured. Always design your DTOs to accurately represent your data model and API contracts.
The transformer handles different types of data:
- Primitive types (string, number, boolean, bigint, symbol, Date): Checked for correct type and coerced if possible
- Classes: Recursively transformed
- Arrays of classes or primitives: Each item is recursively transformed or validated
- Enums: Validated against enum values
Sequelize models are automatically converted to plain objects before transformation. This is done by checking for a 'get' function and calling it with { plain: true }.
The transformer uses a caching mechanism to store prepared metadata for DTOs, which improves performance for subsequent transformations of the same DTO type.
This section details the various ways you can define properties in your DTOs and how the FastTransformInterceptor
handles them.
-
test: number;
- Treated as a primitive type.
- If the value is not a number, it will try to convert to number. If conversion fails, it will throw a
TransformError
.
-
test: string;
- Treated as a primitive type.
- If the value is not a string, it will try to convert to string. If conversion fails, it will throw a
TransformError
.
-
test: boolean;
- Treated as a primitive type.
- If the value is not a boolean, it will try to convert to boolean. If conversion fails, it will throw a
TransformError
.
-
test: bigint;
- Treated as a primitive type.
- If the value is not a bigint, it will try to convert to bigint. If conversion fails, it will throw a
TransformError
.
-
test: symbol;
- Treated as a primitive type.
- If the value is not a symbol, it will throw a
TransformError
.
-
test: Date;
- Treated as a primitive type.
- If the value is not a valid Date object, it will try to create a new Date from the value. If this fails, it will throw a
TransformError
.
-
test: SomeDto;
- Treated as a class type.
- Recursively transformed using the
SomeDto
class definition. - The transformer will apply the same rules to all properties of
SomeDto
.
-
test: SomeDto[];
- Treated as an array of a class type.
- Each item in the array is recursively transformed using the
SomeDto
class definition. - If the value is not an array, it will throw a
TransformError
.
-
test: SomeEnum;
- Treated as an enum type.
- Validated against the enum values.
- If the value is not one of the enum values, it will throw a
TransformError
.
-
test: string | null;
- Allows either a string value or null.
- If the value is a string, it's passed through as-is.
- If the value is null, it's allowed.
- Any other type will throw a
TransformError
.
-
test?: string;
- Optional property (can be undefined).
- If present, must be a string.
- If absent (undefined), it's skipped during transformation.
-
test?: string | null;
- Optional property that can be a string, null, or undefined.
- If present and a string, it's passed through as-is.
- If null, it's allowed.
- If absent (undefined), it's skipped during transformation.
-
@NoCheck() test: any;
- The
@NoCheck()
decorator skips validation for this property. - Any value is allowed and passed through as-is.
- The
-
@NoOut() test: string;
- The
@NoOut()
decorator excludes this property from the output. - It won't appear in the transformed result.
- The
-
@NoCheckPrimitives() class SomeDto { ... }
- The
@NoCheckPrimitives()
decorator skips validation for all primitive types in this class. - Primitive values (number, string, boolean, bigint, symbol, Date) are passed through as-is without type checking.
- The
- Non-primitive types are still validated and transformed as usual.
- This decorator affects nested objects and arrays within the class, respecting the no-check behavior for their primitive properties.
- The transformer attempts to coerce primitive types to their declared type before throwing an error.
- Class types and arrays of class types are recursively transformed.
- Enum types are validated against their defined values.
- Nullable types allow null values in addition to their base type.
- Optional properties (marked with
?
) are skipped if they're undefined. - Required properties that are undefined will throw a
TransformError
. - The
@NoCheck()
decorator can be used on individual properties or entire classes to skip validation. - The
@NoOut()
decorator excludes properties from the final output. - The
@NoCheckPrimitives()
decorator affects all primitive properties in a class, including those in nested objects and arrays.
By understanding these behaviors, you can design your DTOs to precisely control how your API responses are transformed and validated.
The transformer throws a TransformError
when it encounters validation issues. To enable detailed error information, you need to set the debug
configuration to true
in the initialization of FastTransformInterceptor:
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { FastTransformInterceptor } from './fast-transform.interceptor';
@Module({
providers: [
{
provide: APP_INTERCEPTOR,
useFactory: (reflector: Reflector) => {
return new FastTransformInterceptor({
reflector,
debug: true
});
},
inject: [Reflector]
},
],
})
export class AppModule {}
With debug mode enabled, the error response will include detailed information in the following structure:
{
message: string; // A general error message
debug: {
dtoName: string;
fieldName: string;
receivedValue: any;
expectedType?: string;
expectedValues?: any[];
fullObject: any;
filter: Record<string, any>;
stack?: string;
}
}
These errors are caught and converted to BadRequestException
with appropriate error messages and debug information when available.
The FastTransformInterceptor
includes a debug timing feature to help you measure the performance of the transformation process. To enable debug timing:
-
Set the
debug
configuration totrue
. -
When debug mode is enabled, the interceptor will log the time taken for each transformation using
console.time()
andconsole.timeEnd()
. -
The timing information will be logged with the label "fast-transform" for each request.
Example output:
fast-transform: 5.123ms
This feature can help you identify performance bottlenecks in your DTO transformations and optimize your API responses.
The FastTransformInterceptor
uses an internal caching mechanism to store prepared metadata for DTOs. This optimization significantly improves performance for subsequent transformations of the same DTO type. The cache is implemented using a Map
and is automatically managed by the interceptor.
When dealing with Sequelize models, the interceptor automatically converts them to plain objects before transformation. This is done by checking for a 'get' function on the object and calling it with { plain: true }
. This ensures that Sequelize-specific properties are stripped away and only the actual data is transformed.
-
Always use the
@ResponseType
decorator on controller methods that return data. -
Keep your DTOs simple and focused. Use composition for complex structures.
-
Use the
@NoCheck
decorator sparingly, only when absolutely necessary. -
Leverage the
@NoOut
decorator to exclude sensitive information from responses. -
Enable debug mode during development for detailed error information and performance metrics.
-
Regularly review and update your DTOs to ensure they match your API contracts.
-
Use TypeScript's strict mode to catch potential issues early in development.
-
Use the
@NoCheckPrimitives
decorator on DTOs with many primitive fields to improve performance when type checking is not critical. -
Monitor the debug timing outputs to identify and optimize slow transformations.
-
Be aware of how undefined and null values are handled in your DTOs and adjust your designs accordingly.
-
Take advantage of the coercion behavior for primitive types, but be cautious about relying on it for critical data integrity.
-
When working with Sequelize models, be aware that they will be automatically converted to plain objects during transformation.
By following these guidelines and leveraging the full capabilities of the FastTransformInterceptor
, you can ensure consistent, validated, and well-structured API responses in your NestJS application while maintaining high performance.