Express.js 라우터 기능의 오작동

@bbearcookie · March 12, 2023 · 4 min read

문제 상황

독립적인 기능을 담당하는 컨트롤러 로직을 다른 파일로 분할하고, express의 라우터 기능을 이용해서 URI마다 담당하는 로직을 매칭해 줬는데 의도한 대로 동작하지 않았다.

예를 들어 /photo 경로에 대한 라우터와 /voucher 경로에 대한 라우터를 지정하고, 각각 router.get('/') 으로 컨트롤러 로직을 등록했는데 /photo 에 요청해도 /voucher에 요청해도 똑같은 컨트롤러의 로직만 실행되는 것이었다.

원인

원인은 express.Router() 객체를 참조한 것을 각 파일에서 공유해서 사용했기 때문이었다.

Node.js로 API 서버를 개발할 때 설정해야 하는 부분이 express 외에도 여러 부분이 있기에 나는 프로그램의 시작 포인트인 app.js가 아니라 @config/express.js 파일을 따로 만들어서 설정 코드를 작성하는 것을 선호한다.

그래서 @config/express.js 파일에서 app 객체를 생성하고 관련 설정을 한 뒤, app.js 같은 곳에서 사용할 수 있도록 내보냈는데 이 과정에서 express.Router() 객체도 같은 방법으로 내보내서 사용했기 때문에 문제가 생겼던 것이었다.

해결 방법

express.Router() 객체를 생성한 것을 공유해서 사용하지 말고, 각 라우터 모듈에서 새롭게 생성해서 사용해야 한다.

예시

src
├── config
│   ├── express.js
│   ├── router.js
├── router
│   ├── photo.js
│   ├── voucher.js
├── app.js

app.js

const { app, init: initExpress } = require('./config/express');
const { init: initRoute } = require('./route/route');

initExpress(); // express 설정
initRoute(app); // 라우터 경로 설정

app.listen(4000, () => {
  console.log("서버 4000 실행");
})

config/express.js

const express = require('express');
const app = express();
const router = express.Router(); // 라우터는 이렇게 공유해서 사용하면 안된다!
function init() {
  app.use(express.json());
  app.use(express.urlencoded({ extended: true }));
}

module.exports.init = init;
module.exports.app = app;
module.exports.router = router;

config/router.js

const photo = require('../route/photo');
const voucher = require('../route/voucher');

function init(app) {
  app.use('/photo', photo);
  app.use('/voucher', voucher);
}

module.exports.init = init;

router/photo.js

// const { router } = require('../config/express');const express = require('express');
const router = express.Router();

router.route('/')
  .get((req, res) => res.json({ message: '포토 조회 API' }))
  .post((req, res) => res.json({ message: '포토 추가 API' }))
  .delete((req, res) => res.json({ message: '포토 삭제 API' }))

module.exports = router;

router/voucher.js

// const { router } = require('../config/express');const express = require('express');
const router = express.Router();

router.route('/')
  .get((req, res) => res.json({ message: '소유권 조회 API' }))
  .post((req, res) => res.json({ message: '소유권 추가 API' }))
  .delete((req, res) => res.json({ message: '소유권 삭제 API' }))

module.exports = router;

위 프로젝트 구조를 보면config/router.js 에서
/photo의 하위 경로에 대해서는 router/photo.js 파일에 존재하는 미들웨어를 실행하고 /voucher의 하위 경로에 대해서는 router/voucher.js 파일에 존재하는 미들웨어를 실행하게 되어 있다.

이 때 각 파일에서 사용하는 라우터 객체 express.Router()각각의 라우터 파일에서 새로 생성 해야한다.
만약 그렇지 않고 router/photo.js의 주석 처리된 1번 라인 처럼 공유해서 사용한다면, /photo 로 요청해도 /voucher로 요청해도 / 경로에 대해 먼저 등록된 포토카드 관련 컨트롤러만 실행이 되어 버린다.

@bbearcookie
Frontend Developer