Codesigner

[Express] Express로 웹 서버 만들기 (3) - 라우터 본문

Nodejs/Express

[Express] Express로 웹 서버 만들기 (3) - 라우터

eunsukimme 2019. 6. 14. 15:05

저번 포스팅에서 CRUD 기능을 모두 지원하는 할 일 서버를 만들어 보았다. 할 일 외에도 여러 가지 기능이 추가되거나, 다른 여러 프로젝트를 진행하다 보면 server.js 파일의 크기가 점점 커지게 된다. 그러다 보면 코드의 가독성이 점점 떨어지게 되고, 여러 라우트들이 한 파일에 존재하다 보니 디버깅도 어려워지게 된다

다행히도 Express는 이러한 문제를 해결할 수 있게 Router(라우터)라는 기능을 제공해준다. 라우터는 Express 서버의 미니 버전으로, 웹 서버와 같이 라우트 매칭과 요청-응답 처리 기능을 모두 지원한다. 그저 분할되어서 존재할 뿐이며, 서버를 시작하고 특정 포트에서 리스닝하는 것 또한 필요로 하지 않는다. 라우터는 또한 .get(), .put(), .post(), .delete() 등의 라우트 메서드를 모두 지원한다

이번 포스팅에서는 라우터를 사용하여 우리가 만 든 할 일 서버를 라우터로 분리해 보도록 하자. 그리하여 라우터를 생성하고, 해당 라우터를 서버에 적용시키고, 라우터를 사용하였을 때의 라우트 매칭 흐름에 대해서 이해하는 시간을 갖도록하자

 

 

 

익스프레스 라우터

먼저, 우리가 만들었던 할 일 기능을 라우터로 만들어보도록 하자. 라우터 인스턴스를 생성하기 위해선 .Router() 메서드를 호출하면 된다. 생성된 라우터를 사용하기 위해선, 라우터가 어떤 PATH를 다룰 것인지를 app.use()의 첫 번째 파라미터로 명시해주고, 두 번째 파라미터로 라우터를 넘겨주면 된다. 그러면 해당 라우터는 첫 번째 파라미터로 주어진 PATH로 들어오는 요청을 처리할 수 있게 된다. 우리는 먼저 /todos PATH를 처리하는 라우터를 만들어 보도록 하자. 그전에 먼저 다음과 같이 기존 server.js 파일에 존재하는 todo 라우트들을 어딘가에 복사해두고, 지워버린 후 server.js를 다음과 같이 변경해보자

// server.js
const express = require("express");
const app = express();
const PORT = 4001; // 4001번 포트 설정

const todoRouter = require("./todoRouter");    // todoRouter.js 는 잠시 후 만들 것이다

app.use("/todos", todoRouter);    // /todos 경로로 요청이 들어오면 해당 요청을 todoRouter가 처리한다

app.listen(PORT, () => {
  console.log(`Server is listening on port ${PORT}`);
});

만약 GET /todos/1 요청이 서버로 들어온다면, Express는 /todos PATH가 app.use()의 파라미터와 일치하는 것을 확인한다. 그리하여 Express의 라우트 매칭 알고리즘에 의해 해당 요청은 todoRouter 라우터로 전달되고, 나머지 PATH를 해당 라우터 안에서 라우트 하게 된다. 자, 이제 조금 전에 복사한 todo 라우트 로직을 todoRouter.js 에 옮겨보도록 하자. 코드는 다음과 같다

// todoRouter.js
const express = require("express");
const router = express.Router();

const todos = [
  {
    id: 0,
    todo: "저녁 장 보기"
  },
  {
    id: 1,
    todo: "프로그래밍 과제 하기"
  }
];

let todoId = todos.length; // todo 인덱스

router.get("/", (req, res, next) => {
  res.send(todos);
});

router.get("/:id", (req, res, next) => {
  res.send(todos[req.params.id]);
});

router.put("/:id", (req, res, next) => {
  // 해당 id 의 할 일 내용을 수정한다
  const new_todo = req.query.new; // 쿼리 스트링으로 들어온 새로운 할 일

  todos[req.params.id] = { id: Number(req.params.id), todo: new_todo }; // id에 해당하는 todo를 새로운 값으로 변경
  res.send(todos[req.params.id]); // 새로 할당된 todo를 전달한다
});

router.post("/", (req, res, next) => {
  todos.push({ id: todoId, todo: req.query.new }); // 새로운 할 일을 todo에 푸쉬
  todoId++; // todo 인덱스 증가
  res.status(201).send(todos[todoId - 1]); // 새로 만들어진 할 일을 전달한다
});

router.delete("/:id", (req, res, next) => {
  todos.splice(req.params.id, 1); // 파라미터로 주어진 id 의 할 일을 삭제한다
  res.status(204).send(); // 정상적으로 삭제되었음을 알려준다
});

module.exports = router;

눈썰미가 좋은 사람이라면 코드가 수정되었단 걸 알 수 있을 것이다. 모든 라우트의 PATH에서 /todos가 사라졌다. server.js 에서 /todos로 이미 PATH 설정을 해 주었기 때문에, 이 라우터에서는 그 뒤의 PATH만 명시해 주면 된다. 즉, todoRouter의 /:id PATH는 결국 /todos/:id를 의미한다. 이렇게 생성된 라우터를 다른 파일에서 사용하기 위해, module.exports 구문을 사용하여 라우터를 export 하였다. 즉, 이제 다른 파일(server.js)에서 이 라우터를 임포트 할 수 있다. 마지막으로 변경된 것은 router 변수를 생성하여 express 객체의 인스턴스가 아닌 express.Router 객체의 인스턴스를 할당한 것이다. 즉, 기존의 코드와 달라진 것은 세 가지이다. 라우트의 PATH 파라미터, module.exports 구문, 그리고 라우터 객체의 할당 이 세 가지이다

 

 

 

중첩된 라우터의 매칭 과정

위 섹션에서 확인했던 것처럼, 라우터를 활용할 때, PATH가 분할(segmented)된다는 것을 주목할 필요가 있다. 라우터의 라우트 매칭 과정을 도식화한 그림은 다음과 같다

<그림 1> 라우터의 라우트 매칭 과정(출처: codecademy.com)

위 그림에서는 두 가지 라우터가 만들어졌다. 클라이언트의 요청으로 GET /expressions/1 이 들어왔고, 이는 첫 번째 app.use()의 PATH인 /animals와 맞지 않기 때문에, Express 서버는 다음 app.use()의 PATH와 비교하게 된다. 여기서 PATH가 일치하므로, 해당 요청은 expressionsRouter로 전달되고, 해당 요청은 expressions.js 상에 작성된 라우트의 순서대로 비교된다. 첫 번째 라우트에서 HTTP 메서드는 일치하지만 PATH가 / 뿐이므로 /1 파라미터를 매칭 시킬 수 없어서 다음 라우트로 넘어가게 된다. 다음 라우트에서는 HTTP 메서드가 일치함과 동시에 /:id 파라미터를 받아들이므로 해당 라우트로 요청이 전달된다

이런 방식으로 라우터는 중첩되어 사용될 수 있다. 라우터 안에 또 라우터가 존재할 수 있는 것이다. 그리고 요청은 위에서 보였던 순서와 마찬가지로 흘러가게 된다. 이러한 라우트 매칭 흐름은 복잡한 API를 작성하기 위해서 반드시 이해해야 할 필요가 있다

 

 

 

Review

이번 포스팅에서는 라우터를 생성하여 더욱 간결하고 모듈화 된 서버를 만들어 보았다. 서버가 다뤄야 할 요청과 지원해야 하는 기능이 다양해질수록 이렇게 라우터로 분리하여 확장시켜 나가는 것은 매우 중요하다. 이는 코드의 가독성을 높일 뿐만 아니라, 디버깅 시간을 단축시킬 수 있고, 복잡한 API를 효율적으로 설계하고 작성할 수 있다. 여기서는 단지 할 일 기능을 라우터로 만든 것이어서 별 다른 차이를 못 느낄 수 있다. 필자는 독자가 여기에서 그치지 않고 직접 영단어 기능 라우터를 추가해 보는 것을 권장하고 싶다. 그러면서 이러한 라우터를 만드는 이유와 그 필요성에 대해서 좀 더 이해할 수 있는 시간을 가지면 좋을 것 같다

Comments