Injecting request object to a custom validation class in NestJS
Tags: nestjs, typescript, javascript, node
Published: 2021-05-12 11:24:20

I'm a big fan of how NestJS handle validation using class-validator library. There are many advantages of using an external library for validation. For most of the typical cases default integration via `ValidationPipe` is good enough. But as you know, daily work likes to verify and challenge us.

A few days ago I had a specific need – I needed to validate something with ValidatorPipe and class-validator library, but one of the validation factors, was user ID. In this project, user ID is pulled out from JWT token, during the authorization process, and added to the request object.

My first thought was – just use the Injection Request Scope, like we can do it in NestJS services:

```TypeScript
constructor(@Inject(REQUEST) private request: Request) {}
```

Obviously – it doesn't work, otherwise this article wouldn't be here. [Here is a short explanation](https://github.com/nestjs/nest/issues/1955#issuecomment-481991787) made by NestJS creator, Kamil Myśliwiec: 

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kae77w7ow8mx30wlxqxk.png)
 

Ok. So, there is basically no simple way to get request object data in custom validation constraint. But there is a way around! Not perfect, but it works. And if it can't be pretty, at least it should do its job. What steps we need to take, to achieve it?

1. Create Interceptor, which will add the User Object to the request type you need (Query, Body or Param)
2. Write your Validator Constraint, Extended Validation Arguments interface, use the User data you need.
3. Create Pipe, which will strip the request type object from User data context.
4. Create the appropriate decorators, one for each type of request.
5. Use newly created decorators in Controllers, when you need to "inject" User data to your validation class.

Not great, not terrible. Right?
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xnfu77ulb2d1042dtgs0.png)  

## Interceptor
Create Interceptor, which will add User Object to request type you need (Query, Body or Param). For the demonstration purposes, I assume you store your User Object in `request.user` attribute.

```TypeScript
export const REQUEST_CONTEXT = '_requestContext';

@Injectable()
export class InjectUserInterceptor implements NestInterceptor {
  constructor(private type?: Nullable<'query' | 'body' | 'param'>) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest();

    if (this.type && request[this.type]) {
      request[this.type][REQUEST_CONTEXT] = {
        user: request.user,
      };
    }

    return next.handle();
  }
}
```

## Custom validation decorator
Write your Validator Constraint and custom decorator, Extended Validation Arguments interface, use the User data you need.

```TypeScript
@ValidatorConstraint({ async: true })
@Injectable()
export class IsUserCommentValidatorConstraint implements ValidatorConstraintInterface {
  constructor(private commentsRepository: CommentsRepository) {}

  async validate(commentId: number, args?: ExtendedValidationArguments) {
    const userId = args?.object[REQUEST_CONTEXT].user.id;

    if (userId && Number.isInteger(commentId)) {
      const comment = await this.commentsRepository.findByUserId(userId, commentId); // Checking if comment belongs to selected user

      if (!comment) {
        return false;
      }
    }

    return true;
  }

  defaultMessage(): string {
    return 'The comment does not belong to the user';
  }
}

export function IsUserComment(validationOptions?: ValidationOptions) {
  return function (object: any, propertyName: string) {
    registerDecorator({
      name: 'IsUserComment',
      target: object.constructor,
      propertyName: propertyName,
      options: validationOptions,
      validator: IsUserCommentValidatorConstraint,
    });
  };
}
```

If you don't know how to inject dependencies into a custom validator in class-validator library, [this article](https://dev.to/avantar/custom-validation-with-database-in-nestjs-gao) can help you.

My `ExtendedValidationArguments` interface looks like this:

```TypeScript
export interface ExtendedValidationArguments extends ValidationArguments {
  object: {
    [REQUEST_CONTEXT]: {
      user: IUser; // IUser is my interface for User class
    };
  };
}
```

It allows me to use valid typing in `ValidatorConstraint`.  Without it, TypeScript will print out an error, that the `_requestContext` property doesn't exist.


## Stripping Pipe
Create Pipe, which will strip the request type object from User data context. If we don't do that, our DTO object will contain attached previously request data. We don't want that to happen. I'm using here one of the `lodash` function – [omit()](https://lodash.com/docs/4.17.15#omit). It allows removing chosen properties from an object.


```TypeScript
@Injectable()
export class StripRequestContextPipe implements PipeTransform {
  transform(value: any) {
    return omit(value, REQUEST_CONTEXT);
  }
}
```

## New decorators
Creating new decorators is not necessary, but it's definitely a more clean and DRY approach than manually adding Interceptors and Pipes to the methods. We're going to use NestJS built-in function – `applyDecorators`, which [allows merging multiple different decorators into a new one](https://docs.nestjs.com/custom-decorators#decorator-composition).

```TypeScript
export function InjectUserToQuery() {
  return applyDecorators(InjectUserTo('query'));
}

export function InjectUserToBody() {
  return applyDecorators(InjectUserTo('body'));
}

export function InjectUserToParam() {
  return applyDecorators(InjectUserTo('params'));
}

export function InjectUserTo(context: 'query' | 'body' | 'params') {
  return applyDecorators(UseInterceptors(new InjectUserInterceptor(context)), UsePipes(StripRequestContextPipe));
}
```

To add your user data, just decorate your controller's method with one of the above decorators.

```Typescript
  @InjectUserToParam()
  async edit(@Param() params: EditParams){}
```
Now, if you wanted to use your `IsUserComment` decorator in `EditParams`, you will be able to access injected user data.

```Typescript
export class EditParams {
  @IsUserComment()
  commentId: number;
}
```

And that's all! You can use this method, to add any data from the request object to your custom validation class. Hope you find it helpful!

You can find an [example repository on my GitHub](https://github.com/AvantaR/nestjs-validation-tips/tree/main/injecting-request-object-to-a-custom-validation-class).

In case you use in your `ValidationPipe` `whitelist: true` parameter, and above example doesn't work for you – [check this issue](https://github.com/AvantaR/nestjs-validation-tips/issues/1#issue-1183388434).

This article is highly inspired by the idea I've found in [this comment](https://github.com/nestjs/nest/issues/528#issuecomment-497020970) on GitHub.

PS. It's just proof of concept, and this comment ownership validation is a simple example of usage.