Tooling

In the GraphQL world, a lot of articles contemplate how to handle stuff like authentication, or side-effects of operations. Should we put it inside the business logic? Shall we use a higher-order function to enhance queries and mutations as well, for example, with authorization logic? Or maybe use schema directives. There is no single answer.

Nest ecosystem is trying to help with this issue using existing features like guards and interceptors. The idea behind them is to reduce redundancy and also, supply you with tooling that helps create well-structured, readable, and consistent applications.

Overview

You can use either guards, interceptors, filters or pipes in the same fashion as in the simple REST application. Additionally, you are able to easily create your own decorators, by leveraging custom decorators feature. They all act equivalently. Let’s have a look at the following code:

  1. @Query('author')
  2. @UseGuards(AuthGuard)
  3. async getAuthor(@Args('id', ParseIntPipe) id: number) {
  4. return await this.authorsService.findOneById(id);
  5. }

As you can see, GraphQL works pretty well with both guards and pipes. Thanks to that you can, for instance, move your authentication logic to the guard, or even reuse the same guard class as in the REST application. The interceptors works in the exact same way:

  1. @Mutation()
  2. @UseInterceptors(EventsInterceptor)
  3. async upvotePost(@Args('postId') postId: number) {
  4. return await this.postsService.upvoteById({ id: postId });
  5. }

Execution context

However, the ExecutionContext received by both guards and interceptors is somewhat different. GraphQL resolvers have a separate set of arguments, respectively, root, args, context, and info. Hence, we need to transform given ExecutionContext to GqlExecutionContext, which is basically very simple.

  1. import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
  2. import { GqlExecutionContext } from '@nestjs/graphql';
  3. @Injectable()
  4. export class AuthGuard implements CanActivate {
  5. canActivate(context: ExecutionContext): boolean {
  6. const ctx = GqlExecutionContext.create(context);
  7. return true;
  8. }
  9. }

GqlExecutionContext exposes corresponding methods for each argument, like getArgs(), getContext(), and so on. Now we can effortlessly pick up every argument specific for the currently processed request.

Exception filters

The exception filters are compatible with GraphQL applications as well.

  1. @Catch(HttpException)
  2. export class HttpExceptionFilter implements GqlExceptionFilter {
  3. catch(exception: HttpException, host: ArgumentsHost) {
  4. const gqlHost = GqlArgumentsHost.create(host);
  5. return exception;
  6. }
  7. }

info Hint Both GqlExceptionFilter and GqlArgumentsHost are imported from the @nestjs/graphql package.

However, you don’t have access to the native response object in this case (as in the HTTP app).

Custom decorators

As mentioned before, the custom decorators feature works like a charm with GraphQL resolvers as well. Though, the factory function takes an array of arguments, instead of a request object.

  1. export const User = createParamDecorator(
  2. (data, [root, args, ctx, info]) => ctx.user,
  3. );

And then:

  1. @Mutation()
  2. async upvotePost(
  3. @User() user: UserEntity,
  4. @Args('postId') postId: number,
  5. ) {}

info Hint In the above example, we have assumed that your user object is assigned to the context of your GraphQL application.