GraphQL vs REST에서 ALS(AsyncLocalStorage) 사용 분석

ddoachi·2025년 6월 28일

TekaPicker

목록 보기
28/30

git: 1ad8a8c34359cba105d76d40ddafa55b6a4aac46

1. ALS(AsyncLocalStorage)란?

AsyncLocalStorage는 Node.js의 API로, 비동기 작업 간에 컨텍스트(예: 사용자 정보)를 공유하기 위해 사용됨. NestJS에서는 이를 기반으로 요청 단위 사용자 컨텍스트를 유지하려고 사용함.


2. REST에서는 ALS가 잘 동작하는 이유

흐름 구조

요청 → Express Middleware → Controller → Service → 응답
  • RequestContext.run(user, () => next())로 감싸면,
  • 그 다음에 동기적으로 이어지는 모든 로직(Controller, Service 등)에서 context가 유지됨
  • Express의 기본 처리 방식이 이벤트 루프 내에서 동기적으로 연결되기 때문에 ALS context 유지에 문제가 없음

3. GraphQL에서는 ALS가 동작하지 않는 이유

문제의 흐름

요청 → Express Middleware → Apollo Server → GraphQL Resolver
                               ↑
                            (비동기 Task Queue로 분리)
  • next() 이후 Apollo GraphQL 내부에서 resolver 실행이 비동기 Task로 새로 시작됨
  • 이로 인해 AsyncLocalStorage context가 끊어지고, getStore() 호출 시 undefined가 반환됨

요약하자면:

  • ALS는 콜백 내부에서 동기적으로 실행되는 코드에 한해서만 context 유지
  • GraphQL의 resolver는 next() 이후 비동기 환경에서 실행되므로, context가 전파되지 않음

4. 전역 미들웨어는 REST 전용인가?

사실상 전역 NestMiddleware는 REST 요청에 대해서만 ALS 컨텍스트 유지가 가능

요청 종류Middleware에서 ALS 가능 여부설명
REST✅ 가능Express 기반 처리 구조라 next 이후에도 context 유지
GraphQL❌ 불가Apollo Server가 next 이후 비동기 task로 resolver 실행

5. 해결 방법: GraphQL에서는 ALS 대신 context 사용

GraphQL의 context()는 모든 resolver로 전파되므로, 여기에 유저 정보를 직접 넣는 방식이 더 적합함.

Step 1: GraphQLModule 설정

GraphQLModule.forRoot<ApolloDriverConfig>({
  driver: ApolloDriver,
  context: ({ req }) => {
    return {
      req,
      user: req.user, // Passport에서 주입된 user
    };
  },
});

Step 2: Resolver에서 꺼내쓰기

@Query(() => [Item])
async getItems(@Context('user') user: TpUser) {
  console.log('Current user:', user.id);
  ...
}

또는 커스텀 데코레이터 사용

// gql-user.decorator.ts
export const GqlUser = createParamDecorator(
  (data: keyof TpUser | undefined, context: ExecutionContext) => {
    const gqlCtx = GqlExecutionContext.create(context);
    const user = gqlCtx.getContext().user;
    return data ? user?.[data] : user;
  }
);

// 사용 예시
@Query(() => [Item])
async getItems(@GqlUser('id') userId: string) {
  ...
}

🧾 결론

  • (+) REST 요청에서는 RequestMiddleware 안에서 RequestContext.run() 사용 가능
  • (-) GraphQL 요청은 RequestContext.run()이 끊어지므로 ALS 사용 부적합
  • (+) GraphQL에서는 context() 함수 안에서 user를 넘기고, resolver에서는 @Context()나 커스텀 데코레이터로 꺼내는 방식 추천
profile
내일도 풀스택

0개의 댓글