0

I started learning Node.js. And TypeScript. And very often I began to see work with DTOs. But for some reason people in their articles and documentation for frameworks do not give an example of working with user creation and encrypted password.

I often see that either the object from the database is passed to the API directly, or the DTO, which has a password. Example:

const createdUser = new this.userModel(createUserDto)
createdUser.save()

return createdUser

But how can I filter this out? I will clarify, a simple example: a user is created in the database and how now to transfer only safe data to the outside world? I hope for your help, otherwise I'm completely confused.

Colibri
  • 993
  • 11
  • 29

1 Answers1

0

You can use interceptor.

Interceptors have a set of useful capabilities which are inspired by the Aspect Oriented Programming (AOP) technique. They make it possible to:

  • bind extra logic before / after method execution
  • transform the result returned from a function
  • transform the exception thrown from a function
  • extend the basic function behavior

Nestjs

Here is my own way

import {
  CallHandler,
  ExecutionContext,
  Injectable,
  NestInterceptor
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ClassTransformOptions } from '@nestjs/common/interfaces/external/class-transform-options.interface';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Response } from 'express';
import { ClassConstructor, plainToClass } from 'class-transformer';

interface ResponseBody {
  statusCode: number;
  data: unknown;
}

@Injectable()
export class FormatResponse implements NestInterceptor {
  private convertOptions: ClassTransformOptions = {
    excludeExtraneousValues: true,
    enableImplicitConversion: true,
    exposeDefaultValues: true
  };

  constructor(private reflector: Reflector) {}

  intercept(
    context: ExecutionContext,
    next: CallHandler
  ): Observable<ResponseBody> {
    const response: Response = context.switchToHttp().getResponse();

    return next.handle().pipe(
      map(res => {
        const rdto = this.reflector.get('rdto', context.getHandler());

        return {
          statusCode: response.statusCode,
          data: this.convert(rdto, res)
        };
      })
    );
  }

  private convert(rdto: ClassConstructor<unknown>, res: unknown) {
    return rdto
      ? Array.isArray(res)
        ? res.map(el => plainToClass(rdto, el, this.convertOptions))
        : plainToClass(rdto, res, this.convertOptions)
      : res;
  }
}

To define response dto you need to create custom decorator

import { CustomDecorator, SetMetadata } from '@nestjs/common';

export const ResDto = (resDto: unknown): CustomDecorator<string> =>
  SetMetadata('rdto', resDto);

Example

Now you can define response DTO for each route.

DTO

import { Expose } from 'class-transformer';

export class AccountRdto {
  @Expose()
  phone: string;

  @Expose()
  name: string;
}

export class SignUpRdto extends AccountRdto {
  @Expose()
  activeCodeExpires: number;
}

route

@Post('/signup')
@ResDto(SignUpRdto)
async signUp(
  @Res({ passthrough: true }) res: Response,
  @Body() dto: SignUpDto
): Promise<SignUpRdto> {
  const user = await this.authService.signUp(dto);
  return user;
}