Node
Nest.js
Nest Proxy

NestJS Proxy에서 CORS 처리가 필요한 이유

결론

Proxy 응답은 NestJS의 응답 처리 파이프라인을 우회하기 때문에, onProxyRes 콜백에서 수동으로 CORS 헤더를 추가해야 합니다.

일반적인 NestJS 컨트롤러는 app.enableCors()를 통해 자동으로 CORS 헤더가 추가되지만, @Res() 데코레이터를 사용하여 응답을 직접 제어하는 Proxy의 경우에는 NestJS의 응답 후처리 단계가 적용되지 않습니다. NestJS CORS 공식 문서 (opens in a new tab)

NestJS 공식 문서에서 "Enable CORS to allow cross-origin requests from different domains" 라고 명시되어 있으며, app.enableCors() 메서드를 통해 CORS를 활성화할 수 있습니다.

NestJS CORS 미들웨어의 일반적인 동작

NestJS는 app.enableCors() 메서드를 통해 CORS를 간단하게 활성화할 수 있습니다.

const app = await NestFactory.create(AppModule);
app.enableCors({
  origin: ['https://example.com'],
  credentials: true,
});

이렇게 설정하면 NestJS는 응답에 Access-Control-Allow-Origin 등의 CORS 헤더를 자동으로 추가합니다. 이 과정은 NestJS의 응답 처리 파이프라인에서 이루어집니다. NestJS CORS 가이드 (opens in a new tab)

Felix Astner의 가이드에서 "NestJS provides a simple way to enable CORS by using the enableCors() method" 라고 설명하고 있습니다.

@Res() 데코레이터와 NestJS 파이프라인

@Res() 데코레이터를 사용하면 NestJS의 표준 응답 처리 방식을 우회하게 됩니다.

일반적인 NestJS 컨트롤러는 값을 return하면 NestJS가 자동으로 직렬화하고 응답 헤더를 추가하지만, @Res() 데코레이터를 사용하면 개발자가 응답 객체를 직접 제어하게 되어 인터셉터, 예외 필터, CORS 헤더 자동 추가 등의 기능이 적용되지 않습니다. NestJS @Res 사용법 (opens in a new tab)

DEV Community 문서에서 "Using the @Res decorator bypasses features like interceptors, exception filters, and built-in decorators" 라고 명시되어 있습니다.

일반 컨트롤러 (NestJS 파이프라인 적용):

@Get('users')
getUsers() {
  return { users: [...] }; // NestJS가 자동으로 CORS 헤더 추가
}

@Res() 사용 (NestJS 파이프라인 우회):

@Get('users')
getUsers(@Res() res: Response) {
  res.json({ users: [...] }); // CORS 헤더가 자동으로 추가되지 않음
}

http-proxy-middleware와 Proxy 요청의 특수성

http-proxy-middleware는 클라이언트의 요청을 다른 서버로 전달하고, 그 서버의 응답을 클라이언트에 직접 스트리밍합니다.

이 과정에서 proxyRes.pipe(res)를 통해 원본 서버의 응답이 클라이언트로 직접 전달되기 때문에, NestJS의 응답 후처리 단계가 완전히 건너뛰어집니다. http-proxy-middleware 공식 문서 (opens in a new tab)

http-proxy-middleware 문서에서 "The onProxyRes function allows you to add or modify headers in the proxy response" 라고 설명하고 있습니다.

요청 흐름 비교

단계일반 요청Proxy 요청
Request CORS 검증✅ 동작✅ 동작
Controller 실행✅ return 값res 직접 사용
Response Interceptor✅ 동작❌ 우회
Response CORS 헤더✅ 자동 추가❌ 추가 안됨

해결 방법: onProxyRes에서 CORS 헤더 수동 추가

Proxy 응답에 CORS 헤더를 추가하려면 onProxyRes 콜백에서 proxyRes.headers를 직접 수정해야 합니다.

중요: res.setHeader()를 사용하면 proxyRes에 동일한 헤더가 있을 경우 덮어씌워지므로, 반드시 proxyRes.headers를 직접 수정해야 합니다. Stack Overflow - Adding Headers to Proxy Response (opens in a new tab)

Stack Overflow에서 "Headers set on res will be overridden if the response from upstream in proxyRes has the same header set" 라고 명시되어 있습니다.

import { createProxyMiddleware } from 'http-proxy-middleware';
 
const CORS_ALLOWED_ORIGINS = [
  'https://example.com',
  'http://localhost:3000'
];
 
export const proxyMiddleware = createProxyMiddleware({
  target: 'https://api.example.com',
  changeOrigin: true,
  onProxyRes: (proxyRes, req, res) => {
    const requestOrigin = req.headers.origin;
    let allowedOrigin: string | undefined;
 
    // 요청의 origin이 허용된 목록에 있는지 확인
    if (requestOrigin && CORS_ALLOWED_ORIGINS.includes(requestOrigin)) {
      allowedOrigin = requestOrigin;
    }
 
    // proxyRes.headers를 직접 수정 (res.setHeader 사용 안 함!)
    proxyRes.headers = {
      ...proxyRes.headers,
      ...(res.getHeaders() as any),
    };
 
    if (allowedOrigin) {
      proxyRes.headers['access-control-allow-origin'] = allowedOrigin;
      proxyRes.headers['access-control-allow-credentials'] = 'true';
    }
  },
});

왜 proxyRes.headers를 직접 수정해야 하는가?

http-proxy-middleware는 원본 서버의 응답을 클라이언트로 스트리밍할 때, proxyRes.headers에 있는 헤더를 그대로 전달합니다. 만약 res.setHeader()를 사용하면 원본 서버의 응답 헤더가 이를 덮어쓰게 되므로, CORS 헤더가 제대로 전달되지 않습니다.

따라서 proxyRes.headers 객체 자체를 수정하여 원본 서버의 응답 헤더와 함께 CORS 헤더가 클라이언트에 전달되도록 해야 합니다. http-proxy-middleware GitHub (opens in a new tab)

GitHub 문서에서 "The onProxyRes option allows you to modify the proxy response before it's sent to the client" 라고 설명하고 있습니다.

정리

  • NestJS의 일반 컨트롤러는 app.enableCors()로 자동 CORS 처리
  • @Res() 데코레이터 사용 시 NestJS 파이프라인 우회
  • Proxy는 응답을 직접 스트리밍하므로 NestJS의 응답 후처리가 적용되지 않음
  • onProxyRes에서 proxyRes.headers를 직접 수정하여 CORS 헤더 추가

출처