NestJS: How to exclude entity field from returned by controller

Updated: January 1, 2024 By: Guest Contributor Post a comment

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.