Tooling

Tooling

GraphQL ์„ธ๊ณ„์—์„œ ๋งŽ์€ ๊ธฐ์‚ฌ๋Š” ์ธ์ฆ(authentication) ๋˜๋Š” ๋ถ€์ž‘์šฉ(side-effects)๊ณผ ๊ฐ™์€ ํ•ญ๋ชฉ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ๋ถˆํ‰ํ•ฉ๋‹ˆ๋‹ค. ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์— ๋„ฃ์–ด์•ผ ํ•ฉ๋‹ˆ๊นŒ? ์˜ˆ๋ฅผ ๋“ค์–ด ๊ถŒํ•œ ๋ถ€์—ฌ ๋…ผ๋ฆฌ์™€ ๊ฐ™์ด ์ฟผ๋ฆฌ์™€ ๋ฎคํ…Œ์ด์…˜์„ ํ–ฅ์ƒ์‹œํ‚ค๊ธฐ ์œ„ํ•ด ๊ณ ์ฐจ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๊นŒ? ๋˜๋Š” ์Šคํ‚ค๋งˆ ์ง€์‹œ์–ด๋ฅผ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค. ์–ด์จŒ๋“  ํ•œ๊ฐ€์ง€ ๋‹ต์€ ์—†์Šต๋‹ˆ๋‹ค.

Nest ์ƒํƒœ๊ณ„๋Š” guards ๋ฐ interceptors์™€ ๊ฐ™์€ ๊ธฐ์กด ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ ๋ฐฐํ›„์˜ ์•„์ด๋””์–ด๋Š” ์ค‘๋ณต์„ฑ์„ ์ค„์ด๊ณ  ๋˜ํ•œ ์ฒด๊ณ„์ ์ด๊ณ  ์ฝ๊ธฐ ์‰ฝ๊ณ  ์ผ๊ด€์„ฑ ์žˆ๋Š” ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์„ ๋งŒ๋“œ๋Š” ๋ฐ ๋„์›€์ด ๋˜๋Š” ๋„๊ตฌ๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

Overview

๊ฐ„๋‹จํ•œ REST ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ์™€ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ guards, interceptors, filters ๋˜๋Š” pipes๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ custom decorators ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•˜์—ฌ ์ž์‹ ๋งŒ์˜ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‰ฝ๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋“ค์€ ๋ชจ๋‘ ๋™๋“ฑํ•˜๊ฒŒ ํ–‰๋™ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ๋ณด์ž.

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

๋ณด์‹œ๋‹ค์‹œํ”ผ GraphQL์€ ๊ฐ€๋“œ์™€ ํŒŒ์ดํ”„ ๋ชจ๋‘์—์„œ ์ž˜ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์ธ์ฆ ๋กœ์ง์„ ๊ฐ€๋“œ๋กœ ์ด๋™ํ•˜๊ฑฐ๋‚˜ REST ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ์™€ ๋™์ผํ•œ ๊ฐ€๋“œ ํด๋ž˜์Šค๋ฅผ ์žฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ธํ„ฐ์…‰ํ„ฐ๋Š” ๋˜‘๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

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

Execution context

๊ทธ๋Ÿฌ๋‚˜ ๊ฐ€๋“œ์™€ ์ธํ„ฐ์…‰ํ„ฐ ๋ชจ๋‘์— ์˜ํ•ด ์ˆ˜์‹ ๋˜๋Š” ExecutionContext๋Š” ์•ฝ๊ฐ„ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. GraphQL ๋ฆฌ์กธ๋ฒ„์—๋Š” ๊ฐ๊ฐ root, args, context ๋ฐ info์™€ ๊ฐ™์€ ๋ณ„๋„์˜ ์ธ์ˆ˜ ์„ธํŠธ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ฃผ์–ด์ง„ ExecutionContext๋ฅผ GqlExecutionContext๋กœ ๋ณ€ํ™˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ ๋งค์šฐ ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค.

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const ctx = GqlExecutionContext.create(context);
    return true;
  }
}

GqlExecutionContext๋Š” getArgs(), getContext()๋“ฑ๊ณผ ๊ฐ™์€ ๊ฐ ์ธ์ˆ˜์— ํ•ด๋‹นํ•˜๋Š” ๋ฉ”์†Œ๋“œ๋ฅผ ๋…ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ์ด์ œ ํ˜„์žฌ ์ฒ˜๋ฆฌ๋œ ์š”์ฒญ์— ํŠน์ •ํ•œ ๋ชจ๋“  ์ธ์ˆ˜๋ฅผ ์†์‰ฝ๊ฒŒ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Exception filters

์˜ˆ์™ธ ํ•„ํ„ฐ๋Š” GraphQL ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ๊ณผ๋„ ํ˜ธํ™˜๋ฉ๋‹ˆ๋‹ค.

@Catch(HttpException)
export class HttpExceptionFilter implements GqlExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const gqlHost = GqlArgumentsHost.create(host);
    return exception;
  }
}

info ํžŒํŠธ GqlExceptionFilter์™€ GqlArgumentsHost๋Š” ๋ชจ๋‘ @nestjs/graphql ํŒจํ‚ค์ง€์—์„œ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ์ด ๊ฒฝ์šฐ HTTP ์•ฑ์—์„œ์™€ ๊ฐ™์ด ๊ธฐ๋ณธ response๊ฐ์ฒด์— ์•ก์„ธ์Šค ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

Custom decorators

์•ž์—์„œ ์–ธ๊ธ‰ํ–ˆ๋“ฏ์ด custom decorators ๊ธฐ๋Šฅ์€ GraphQL ๋ฆฌ์กธ๋ฒ„์˜ ๋งค๋ ฅ์ฒ˜๋Ÿผ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ํŒฉํ† ๋ฆฌ ํ•จ์ˆ˜๋Š” response๊ฐ์ฒด ๋Œ€์‹  ์ธ์ˆ˜ ๋ฐฐ์—ด์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

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

๊ทธ๋ฆฌ๊ณ :

@Mutation()
async upvotePost(
  @User() user: UserEntity,
  @Args('postId') postId: number,
) {}

info ํžŒํŠธ ์œ„์˜ ์˜ˆ์—์„œ, user ๊ฐ์ฒด๊ฐ€ GraphQL ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ปจํ…์ŠคํŠธ์— ํ• ๋‹น๋˜์—ˆ๋‹ค๊ณ  ๊ฐ€์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.

Last updated

Was this helpful?