[Nest] GraphQL Subscription 적용기

ShinJuYong·2022년 6월 22일
1

Nest

목록 보기
9/13
post-thumbnail

왜했니?

기업협업 프로젝트를 진행중 알림기능이 있어 알림기능을 구현중 DB를쓰는거보단 실시간 통신을위한 소켓을 쓰는게 나아보여 알아보다가
GraphQL Subscription이란게 있어 적용시켜보려 했다

왜 안됐니

기존 GraphQL로 들어오는 Context구조가 (Req,Res)로 만들어둔 상태라서 Req로 들어온 JWT토큰 사용했고 만약 최초 로그인을하면 Res에 setHeader를 통해 리프레쉬 토큰을 넣어주는 형태였다

근데 Subscription을 쓰는 레퍼런스 코드를 보니 죄다

context: ({ req, connection }) => {
  const TOKEN_KEY = 'x-jwt';
  // http
  if (req) {
    return { token: req.headers[TOKEN_KEY] }; // 헤더에 있는 토큰만 쓸거니 헤더 중 토큰만 솎아서 보내자
  }
  // ws. connection은 ws 연결시 딱 한 번만 반환됨
  if (connection) {
    return { token: connection.context[TOKEN_KEY] }; // connection.context 에 토큰이 들어 있다
  }
},

이런 형태로 만들어진거다.. 그래서 우리 프로젝트에는 이런 형태로쓰면 setHeader가불가능한디.. 이러면서 찾고 찾다가
darren님의 블로그 내용중

매 요청마다 들어오는 req와는 달리 connection은 ws 연결되는 시점 1번만 출력된다.

라는 내용이있어 혹시 이부분을 GraphQL 모듈단에서 처리못하나? 하고 구글링을해서 찾아낸걸 적용해보니 Context부분을 해결했다!

      subscriptions: {
        'subscriptions-transport-ws': {
          onConnect: (headersRaw: Record<string, unknown>) => {
            console.log(headersRaw);
            const headers = Object.keys(headersRaw).reduce((dest, key) => {
              dest[key.toLowerCase()] = headersRaw[key];
              console.log(dest);
              return dest;
            }, {});
            return {
              req: {
                headers: headers,
              },
            };
          },
        },
      },

onConnect를 통해 연결시에 들어온 Header를 읽어 유저정보를 구분할수있게 만들었다

적용 결과

아래의 API는 예시용으로 만든 API입니다.

  1. PubSub을 글로벌로 만들어 사용할수있게했다
import { Global, Module } from '@nestjs/common';
import { PubSub } from 'graphql-subscriptions';

@Global()
@Module({
  providers: [{ provide: 'PUB_SUB', useValue: new PubSub() }],
  exports: ['PUB_SUB'],
})
export class pubsubModule {}
  1. 그후 PubSub을 주입받은곳에서 Subscription을 할수있는 GQL API를 만들었다

Inject 부분

@Resolver()
export class CourseReviewResolver {
  constructor(
    private readonly courseReviewService: CourseReviewService,
    @Inject('PUB_SUB')
    private readonly pubSub: PubSub,
  ) {}

API 부분

 @Mutation(() => CourseReview)
  async createCourseReview(
    @Args('createCourseReviewInput')
    createCourseReviewInput: CreateCourseReviewInput,
  ) {
    const createdResult = await this.courseReviewService.create(
      createCourseReviewInput,
    );
    
    const pub: CourseReviewPubSub = {
      reviewId: createdResult.id,
      userId: createdResult.user.userId,
      courseTitle: createdResult.course.title,
    };

    this.pubSub.publish('reviewAdded', { reviewAdded: pub });

    return createdResult;
  }

강좌에대한 리뷰를 만들게되면 reviewAdded라는 이름으로 소켓통신을 시작하고 reviewAdded: pub의 내용을 보내게된다

  @UseGuards(GqlAuthAccessGuard)
  @Subscription(() => CourseReviewPubSub, {
    filter: (result, _, context) => {
      return result.reviewAdded.userId === context.req.user.userId;
    },
  })
  reviewAdded() {
    return this.pubSub.asyncIterator('reviewAdded');
  }

그러면 asyncIterator를통해 reviewAdded라는 이름의 소켓통신이 들어오게되면 pub이라는 객체가 들어오는데 위의

@UseGuards(GqlAuthAccessGuard)
@Subscription(() => CourseReviewPubSub, {
    filter: (result, _, context) => {
      return result.reviewAdded.userId === context.req.user.userId;
    },
  })

부분에서 현재 유저리뷰글을 작성한 유저가 동일할때만 알람이 오게 설정을 해서 메x플의 확성기마냥 모든 알람이 들어오지 않도록 필터링을 거치게한다.

위의 예시 API를 만든후
댓글알람,좋아요알람,맨션알람등 다른곳에 적용해 실사용할 예정이다

참고한곳


TypeORM + GQL + Nest
Passport쓸때 Subscription 적용하기

0개의 댓글