Express Middleware 타입 충돌 문제

henry·2025년 2월 4일

Express 애플리케이션을 TypeScript로 작성할 때, 미들웨어에서 Request 객체를 확장하여 사용자 정보를 추가하려는 과정에서 Express의 기본 타입확장된 타입 간의 충돌 문제가 발생의 원인과 해결 방법에 대해 정리


📌 문제 상황

사용자 인증 미들웨어(verifyToken 함수)를 구현하면서 JWT(JSON Web Token)를 디코딩한 후 req.user사용자 정보를 추가하고 싶었습니다. TypeScript에서는 req.user가 기본적으로 존재하지 않기 때문에 Request 객체를 확장한 커스텀 타입을 정의했습니다.

문제 코드

  • AuthMiddleware.ts

    import { Request, Response, NextFunction } from 'express';
    import httpStatus from 'http-status';
    import jwt, { JwtPayload } from 'jsonwebtoken';
    import dotenv from 'dotenv';
    
    dotenv.config();
    
    export interface JwtRequest extends Request {  // Request를 확장한 부분
       user?: string | JwtPayload;
    }
    
    export const verifyToken = (req: JwtRequest, res: Response, next: NextFunction) => {
       try {
          const token = req.headers.authorization?.split(' ')[1];
    
          if (!token) {
             return res.status(httpStatus.UNAUTHORIZED).json({ message: '토큰이 제공되지 않았습니다.' });
          }
    
          const secret = process.env.JWT_ACCESS_SECRET_KEY!;
          const decoded = jwt.verify(token, secret);
    
          req.user = decoded;
    
          next();
       } catch (error) {
          console.error('❌ 유효하지 않은 토큰입니다:', error);
          return res.status(httpStatus.UNAUTHORIZED).json({ message: '유효하지 않은 토큰입니다.' });
       }
    };
  • ProductRoutes.ts

    import express from 'express';
    import { getProducts, registProducts } from '../controllers/ProductController';
    import { verifyToken } from '../middleware/AuthMiddleware';
    
    const router = express.Router();
    
    router.get('/products', getProducts);
    router.post('/regist', verifyToken, registProducts);
    
    export default router;

    해당 코드를 실행하면 아래와 같은 타입 충돌 에러가 발생합니다.


📌 왜 발생했을까?

이 에러는 Express의 기본 타입과 커스텀 타입 간의 불일치 때문에 발생합니다.

  • verifyToken 함수의 타입:

    • req의 타입을 확장하여 JwtRequest로 정의했지만,
      이는 Express의 기본 Request 타입과 다릅니다.
  • Express 라우터의 기대 타입:

    • 라우터는 미들웨어 함수의 타입으로 기본적으로 RequestHandler를 기대합니다.
      RequestHandler는 Express의 기본 Request 타입만을 사용해야 합니다.

타입 충돌 요약

  • 커스텀 타입 : JwtRequest (Request 확장)
  • Express 기본 타입 : RequestHandler (Request 기본)

Express는 확장된 타입(JwtRequest)을 RequestHandler로 간주하지 못하므로 타입 불일치 에러가 발생


📌 해결 방법

글로벌 타입 확장

  • 글로벌 타입 확장 방법은 reqJwtRequest로 타입 단언하는 방법보다 타입 안정성유지 보수에 용이합니다.

    • 타입 단언 예시
    		(req as JwtRequest).user = decoded;
  • Express의 기본 Request 객체를 글로벌로 확장하여 모든 곳에서 req.user를 안전하게 사용할 수 있도록 설정합니다.

    • src/types/express.d.ts 파일 생성

        import { JwtPayload } from 'jsonwebtoken';
      
        declare global {
           namespace Express {
              export interface Request {
                 user?: string | JwtPayload;
              }
           }
        }
    • verifyToken 함수 수정

      export const verifyToken = (req: Request, res: Response, next: NextFunction): void => {
         try {
            const token = req.headers.authorization?.split(' ')[1];
      
            if (!token) {
               res.status(httpStatus.UNAUTHORIZED).json({ message: '토큰이 제공되지 않았습니다.' });
               return;
            }
      
            const secret = process.env.JWT_ACCESS_SECRET_KEY!;
            const decoded = jwt.verify(token, secret);
      
            // req를 JwtRequest로 타입 단언
            (req as JwtRequest).user = decoded;
      
            next();
         } catch (error) {
            console.error('❌ 유효하지 않은 토큰입니다:', error);
            res.status(httpStatus.UNAUTHORIZED).json({ message: '유효하지 않은 토큰입니다.' });
         }
      };

0개의 댓글