Introduction
When building an API with NestJS, it’s essential to control what data is sent back to the client. At times, it’s necessary to exclude certain entity fields for security or relevancy reasons. This article dives deep into various methods to hide specific properties in a NestJS application.
Using Class Transformer
To begin with, NestJS has seamless integration with class-transformer, a powerful tool that allows us to control the serialization of objects. By adding the @Exclude()
decorator to a class property, we tell NestJS to leave out this field when the class is converted to a plain object.
import { Exclude } from 'class-transformer';
class UserEntity {
id: number;
@Exclude()
password: string;
// ... other properties
}
Make sure you then use ClassSerializerInterceptor
to apply the transformation globally or in a specific controller.
@Controller('users')
@UseInterceptors(ClassSerializerInterceptor)
export class UsersController {
// ...
}
Custom Decorators for Hiding Fields
Building on the basics, we can create custom decorators that provide more flexibilty. This allows us to create on-the-fly exclusion logic tailored to specific use cases or business rules.
// CustomDecorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const CustomExclude = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const response = ctx.switchToHttp().getResponse();
response.locals.excludeFields = []; // Define logic to set fields based on context
}
);
You can then retrieve these fields in an interceptor and manipulate the response accordingly.
Dynamic Exclusion with Interceptors
In scenarios where exclusion rules can’t be statically defined, such as when they are role-based or context-specific, we can implement an interceptor that determines what to exclude during runtime.
// DynamicFieldInterceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { map } from 'rxjs/operators';
@Injectable()
export class DynamicFieldInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler) {
// Dynamic logic to figure out which fields to exclude
return next.handle().pipe(map(data => {
// Exclude fields dynamically from data
return data;
}));
}
}
PartialType and OmitType with Mapped Types
NestJS offers Mapped Types in the form of PartialType
and OmitType
that are handy when you need to derive DTOs from your entities and exclude fields without affecting the entity itself.
// user.dto.ts
import { OmitType } from '@nestjs/swagger';
import { UserEntity } from './user.entity';
export class UserDto extends OmitType(UserEntity, ['password'] as const) {
// Now UserDto excludes the password field from UserEntity
}
Selective Exclusions with Query Builders
For ultimate control over the database response, using a Query Builder gives you fine-grained control by allowing you to specify exactly which columns to select, thereby implicitly excluding undesired ones.
// UserController.ts
// ... other imports
import { getConnection } from 'typeorm';
@Controller('users')
export class UsersController {
// ...
@Get()
async findAll(): Promise<UserEntity[]> {
return getConnection()
.createQueryBuilder()
.select(['user.id', 'user.name']) // Explicitly select only certain fields
.from(UserEntity, 'user')
.getMany();
}
}
Conclusion
Throughout this tutorial, we’ve explored multiple avenues for excluding entity fields when responding to HTTP requests in NestJS. By harnessing decorators, interceptors, DTOs, and Query Builders, we have the tools to shape our API responses to be both secure and relevant to the consumer. As our NestJS applications grow in complexity, these techniques will be pivotal in maintaining good API hygiene.