목록으로

HTTP Module

약 7분 읽기Development#Development#NestJS#모노레포댓글로 이동

토큰을 보내고 싶어요

main
date
Jul 25, 2024
slug
http-module
author
status
Public
tags
Development
NestJS
모노레포
summary
토큰을 보내고 싶어요
type
Post
thumbnail
category
Development
updatedAt
Oct 19, 2025 02:09 PM
신규 프로젝트로 유니서베이 마그레이션 작업을 진행하고있다.
프론트를 모노레포 구조로 바꾸면서도 쉽지 않았는데 백엔드도 모노레포 구조로 만들려고 하니 순탄치 않았다.
유니서베이도 백엔드 서버가 4개 정도가 된다. (플랫폼, 워커, 소켓, 라우터) 이들 모두 서버는 나눠 져 있지만 서로 파일을 공유하고 있기 때문에 모노레포 구조로 할 경우 반복 작업을 피할 수 있다.
 
먼저 프론트를 작업 하면서 api 호출할 때 header에 토큰을 실어서 보냈기 때문에 문제가 되지 않았었다.
하지만 호출 방식을 서버 투 서버로 하자라는 팀장님의 제안이 있었고 거기에 맞춰서 작업을 들어가는데 처음에 수월하게 진행되는 듯 보였다.
 
// ResultLegacyService.ts @Injectable() export class ResultLegacyService { private readonly axios: AxiosInstance; constructor( private readonly configService: ConfigSerivce) { const axiosConfig = {// ..생략 } this.axios = createAxiosInstance(axiosConfig); } } // ResultLegacyModule.ts @Module({ import: [UnisurveyConfigModule, ConfigModule], }) export class ResultLegacyModule {} // ProjectLegacyService.ts @Injectable() export class ProjectLegacyService { private readonly axios: AxiosInstance; constructor( private readonly configService: ConfigSerivce) { const axiosConfig = {// ..생략 } this.axios = createAxiosInstance(axiosConfig); } } // ProjectLegacyModule.ts @Module({ import: [UnisurveyConfigModule, ConfigModule], }) export class ProjectLegacyModule {}
 
얼핏 봐도 상당히 불편해 보인다. 백엔드 서버에서 HTTP 요청을 할 일이 많지 않아서 편하게 axios 인스턴스를 사용했었는데 기본 플랫폼 서버에 임시 방편으로 데이터를 요청 해야 하기 때문에 매 요청은 서버에서 서버로 요청을 하게 된다.
계속 모듈마다 Config 모듈을 주입하고 axios 인스턴스를 생성해주고 있다. 해서 다른 방법을 찾아보다가 @nestjs/axios를 활용해보기로 했다.
 
import { Global, Module } from '@nestjs/common'; import { HttpModule } from '@nestjs/axios'; import { ConfigModule, ConfigService } from '@nestjs/config'; @Global() @Module({ imports: [ ConfigModule, HttpModule.registerAsync({ imports: [ConfigModule], useFactory: async (configService: ConfigService) => ({ timeout: 5000, maxRedirects: 1, baseURL: `${configService.get('UNISURVEY_API_HOST')}/api`, }), inject: [ConfigService], }), ], providers: [], exports: [HttpModule], }) export class AxiosModule {}
 
axios 모듈을 생성하였고 이를 SharedModule에 주입 시켜주었다.
모듈 이름 그대로 애플리케이션 전반적으로 필요한 모듈을 주입하는 곳이다.
 
import { Module } from '@nestjs/common'; import { ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler'; import { APP_GUARD } from '@nestjs/core'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { I18nModule } from '@app/shared/modules/i18n/i18n.module'; import { UnisurveyConfigModule } from '@app/shared/config/unisurvey.config.module'; import * as path from 'path'; import { HttpModule } from '@nestjs/axios'; export const envFilePath = [path.resolve(__dirname, '../../../.env')]; @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, envFilePath, }), HttpModule.registerAsync({ imports: [ConfigModule], useFactory: async (configService: ConfigService) => ({ timeout: 5000, maxRedirects: 1, baseURL: `${configService.get('UNISURVEY_API_HOST')}/api`, }), inject: [ConfigService], }), I18nModule, ThrottlerModule.forRoot([ { ttl: 1000, limit: 10, }, ]), UnisurveyConfigModule, ], providers: [ { provide: APP_GUARD, useClass: ThrottlerGuard, }, ], controllers: [], exports: [], }) export class SharedModule {}
 
위 모듈을 메인인 app.module에 import 해주면 준비는 끝난다.
 
import { Logger, MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { SharedModule } from '@app/shared'; import { LoggerMiddleware } from '@app/shared/middlewares/logger.middleware'; import { LegacyModule } from '@app/api/modules/legacy/legacy.module'; import { AuthModule } from '@app/api/modules/auth/auth.module'; @Module({ imports: [SharedModule, LegacyModule, AuthModule], controllers: [AppController], providers: [AppService, Logger], }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer.apply(LoggerMiddleware).forRoutes('*'); } }
 
모듈을 주입했으면 필요한 서비스에서 HttpService를 불러와 사용하면 된다.
 
@Injectable() export class ResultLegacyService { private readonly logger = new Logger(ResultLegacyService.name); constructor(private readonly httpService: HttpService) {} async getQuestionInfo(SNUM: number) { try { const url = `/result/question/${SNUM}`; const { data } = await this.httpService.axiosRef.get(url); return data; } catch (e) { this.logger.error( `프로젝트 번호 : ${SNUM} 프로젝트 정보 조회 실패 ERROR ${e}`, ); } } }
 
매 모듈마다 주입해줬던 config도 글로벌로 설정했기 때문에 필요 없어진다.
 
import { Module } from '@nestjs/common'; import { ResultLegacyController } from '@app/api/modules/legacy/result/result.controller'; import { ResultLegacyService } from '@app/api/modules/legacy/result/result.service'; @Module({ imports: [], controllers: [ResultLegacyController], providers: [ResultLegacyService], exports: [ResultLegacyService], }) export class ResultLegacyModule {}
 
하지만 한 가지 문제가 있다. 클라이언트에서 위 서버로 요청을 보낼 때에는 쿠키에 토큰이 담겨져 있어 정상적으로 요청이 되지만 위 Nest 서버에서 플랫폼 Nest 서버로 요청을 보낼 때 header에 토큰을 담아야 하는데 토큰이 담기지 않아 401 에러가 발생… 😥
 
HTTP Module 내에서도 interceptor를 활용할 수 있어서 그 방법대로 진행을 해보는데 request 접근이 안된다..
 
import { Global, MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { HttpModule, HttpService } from '@nestjs/axios'; import { ConfigModule, ConfigService } from '@nestjs/config'; import * as cookie from 'cookie'; @Global() @Module({ imports: [ ConfigModule, HttpModule.registerAsync({ imports: [ConfigModule], useFactory: async (configService: ConfigService) => ({ timeout: 5000, maxRedirects: 1, baseURL: `${configService.get('UNISURVEY_API_HOST')}/api`, }), inject: [ConfigService], }), ], providers: [CookieMiddleware], exports: [HttpModule], }) export class AxiosModule { constructor(private readonly httpService: HttpService) { this.httpService.axiosRef.interceptors.request.use( async (config: any) => { console.log(config.headers['token']) // null return config; }, (error) => { return Promise.reject(error); }, ); } }
 
다른 방법을 찾아보다가 middleware를 활용하는 방법이 있어서 적용 시켜 보았다.
 
import { Global, MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { HttpModule, HttpService } from '@nestjs/axios'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { CookieMiddleware } from '@app/shared/middlewares/cookie.middleware'; @Global() @Module({ imports: [ ConfigModule, HttpModule.registerAsync({ imports: [ConfigModule], useFactory: async (configService: ConfigService) => ({ timeout: 5000, maxRedirects: 1, baseURL: `${configService.get('UNISURVEY_API_HOST')}/api`, }), inject: [ConfigService], }), ], providers: [CookieMiddleware], exports: [HttpModule], }) export class AxiosModule implements NestModule { constructor(private readonly httpService: HttpService) { this.httpService.axiosRef.interceptors.request.use( async (config: any) => { console.log(config.headers['token']) // null return config; }, (error) => { return Promise.reject(error); }, ); } configure(consumer: MiddlewareConsumer) { consumer.apply(CookieMiddleware).forRoutes('*'); // 미들웨어 추가 } } /*===========================================================================*/ import { Injectable, NestMiddleware } from '@nestjs/common'; import { NextFunction, Request, Response } from 'express'; import cookieParser from 'cookie-parser'; @Injectable() export class CookieMiddleware implements NestMiddleware { use(req: Request, res: Response, next: NextFunction) { cookieParser()(req, res, () => { const token = req.cookies['token']; if (token) req.headers['Authorization'] = `Bearer ${token}`; next(); }); } }
 
미들웨어에서는 토큰을 정상적으로 추출할 수 있어서 왠지 될 것 처럼 보였지만… 여전히 서버 2로 요청을 보낼 때 header에 토큰이 담기지 않는다…음.. 왜 안 담길까..?? 🤔
 
좀 더 찾아보니 이번엔 인터셉터를 활용한 방법이 있었다.
 
HTTP Service 요청을 인터셉터 해서 토큰을 확인하고 만약 토큰이 존재한다면 header에 실어주는 방식
 
import { CallHandler, ExecutionContext, Injectable, NestInterceptor, } from '@nestjs/common'; import { HttpService } from '@nestjs/axios'; import { Observable } from 'rxjs'; @Injectable() export class HttpServiceInterceptor implements NestInterceptor { constructor(private httpService: HttpService) {} intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const ctx = context.switchToHttp(); const request = ctx.getRequest(); const cookies = request.headers['cookie']; let token = ''; if (cookies) { const parsedCookies = cookies.split('; ').reduce((acc, cookie) => { const [name, value] = cookie.split('='); acc[name] = value; return acc; }, {}); token = parsedCookies['token']; this.httpService.axiosRef.interceptors.request.use((config) => { if (token) { config.headers['Authorization'] = `Bearer ${token}`; } return config; }); } return next.handle().pipe(); } }
 
header에 cookie를 확인해서 토큰을 추출한다. 추출 후에 토큰이 존재한다면 headers에 심어준다.
 
async function bootstrap() { const app = await NestFactory.create(AppModule); // ... 이하 생략 const httpService = app.get(HttpService); app.useGlobalInterceptors(new HttpServiceInterceptor(httpService)); // 글로벌 설정 } bootstrap();
 
설레는 마음을 안고 요청을 보냈을 때 반가운 200 코드를 확인했다.
 
notion image
 
401 안녕👋