Express.js - 2. 라우팅

클라이언트가 서버로 접속할때는 특정한 URL를 통해 접속한다. 서버에서는 이 URL에 해당하는 자원을 클라이언트로 보내준다. 혹은 POST요청일 경우는 자원을 만들기도 한다. 이러한 클라이언트 요청을 위한 URL 스키마를 라우트라고 한다. 서버에서는 라우팅 작업을 통해 클라이언트와 통신의 인터페이스를 제공해 준다. 익스프레스에서 중요한 것 중 하나가 이 라우팅 모듈이다. 본 글에서는 익스프레스 4.x를 기준으로 라우팅 기능에 대해 설명한다.

HTTP verbs

우선 기본적인 지식부터 확인하고 가자. HTTP 리퀘스트에는 메쏘드(method) 개념이 있다. html에서 폼요청시 mehtod 속성을 설정하는데 보통 GET, POST를 사용한다. 이외에도 표준 스펙에서는 HEAD, PUT, DELETE, TRACE, OPTIONS, CONNECT 등 10가지 이상의 메소드를 정의한다. 서비스를 구현할 때는 보통 REST API를 위해 4가지 메소드만 사용해도 충분하다.

  • POST
  • GET
  • PUT
  • DELETE

이 메소드는 동사 역할을 한다. 동사에 대한 목적어가 바로 URI다. 예를 들어 GET 메쏘드를 이용해 /users 라는 URI를 호출하면 유저 정보를 조회를 요청하는 프로토콜이라고 해석한다. 같은 URI에 POST 메쏘드로 호출하면 새로운 유정 정보를 서버에 추가하는 요청이다.  이러한 구조로 익스프레스의 라우팅을 구현한다.

라우팅 정의

기본적인 라우팅 설정 코드는 아래와 같다.

var express = require("express")
var app = express()

app.get("/users", function (req, res) {
  res.send("list of users")
})

익스프레스 객체를 담고있는 app변수는 HTTP 메쏘드 명에 해당하는 함수를 가지고 있다.

  • app.post()
  • app.get()
  • app.put()
  • app.delete()

각 함수의 첫번째 파라매터에는 서버자원을 가리키는 URI 문자열을 지정한다. 두번째 파라매터는 라우팅 로직 함수를 콜백 형태로 구현한다. 즉 설정한 URI의 메소드로 요청이 들어오면 두번째 파라매터에 구현한 함수가 동작하는 것이다.

콜백 함수를 살펴보자. 첫번째 파라매터는 리퀘스트 객체(req)로서 클라언트 요청 정보를 담고 있다. 요청 쿼리나 요청 바디 접근시 사용한다. 페이지네이션을 위해 'GET /users?limit=10&skip=30' 요청의 경우를 살펴보자. limit, skip의 쿼리값을 req.param() 함수로 확인할 수 있다.

app.get("/users", function (req, res) {
  res.send(
    "list of users. limit: " +
      req.param("limit") +
      " skip: " +
      req.param("skip")
  )
})

두번째 파라매터는 클라이언트 응답을 위한 객체이다. 위 샘플코드에서는 res.send() 함수를 이용하여 'list of users'라는 문자열로 응답하고 있다.

라우팅 테스트를 하기위해서는 다양한 메소드 요청을 지원하는 포스트맨 크롬익스텐션을 활용하면 편리하다. 아래 결과를 참고하자.

URI 파라메터

모든 고객 리스트를 조회하는 것이 아니라 특정 1명의 정보를 조회할때는 어떤 URI로 접근해야할까?

GET /users/1

1번 고객 정보에 대한 요청이라고 할 수 있다. 1은 유저 식별자에 따라 2, 3, 4, .. 가 될 수 있다. 이러한 경우 GET /users/:id라고 설정하고 리퀘스트 객체를 이용해 req.params.id로 접근하여 고객 아이디 값을 얻을 수 있다. 코드로 확인해 보자.

app.get("/users/:id", function (req, res) {
  res.send("user id: " + req.params.id)
})

응답

라우트 로직을 구현한 뒤 요청한 클라이언트로 결과를 보내줘야 한다. 결과는 문자열이 될 수도 있지만 파일, html 문서나 상태코드가 될 수 도 있다. 특별한 경우에는 바디없이 HTTP 헤더만 보낼수도 있다. 리스판스 객체(res)는 이러한 기능을 각 각의 함수로 구현해 놓았다.

  • res.send(): 문자열로 응답한다.
  • res.json(): Json 객체로 응답한다.
  • res.render(): Jade같은 템플릿을 렌더링한다.
  • res.sendfile(): 파일 다운로드로 응답한다.
  • res.set(): 헤더 값을 세팅한다. 세팅 후 res.send()를 호출하면 바디없이 헤더만 보낼 수 있다.
`res.send()` 함수는 1) 문자열이나 객체 파라매터를 넣어 호출하면 바디로 그 값을 담아서 응답한다. 2) 그러나 200, 400 같은 HTTP 상태 코드를 설정해서 보내면 바디 없이 상태 코드를 담은 헤더만 보낼 수 있다. 3) 첫번째 파라메터에 HTTP 상태 코드를 넣고 두번째는 문자열이나 객체를 넣을 경우 상태코드를 설정한 헤더와 바디를 함께 전송한다. 아래 코드로 확인해 보자.
res.send(204)

res.send(202, "with text")

라우팅 모듈화

본격적으로 라우팅을 구조화 해보자.

app.use("/users", require("./routes/users"))

메소드와 상관없이 /usres로 시작하는 URI는 /routes폴더에 위치할 users 모듈에서 처리하도록 했다. /route/users/index.js 파일을 살펴보자.

var express = require("express")
var router = express.Router()

router.get("/", function (req, res, next) {
  res.send("list of users")
})

router.get("/new", function (req, res, next) {
  res.render("index", { title: "Add User Form" })
})

router.get("/:id", function (req, res, next) {
  res.send("a user. id: " + req.params.id)
})

router.post("/", function (req, res, next) {
  res.send("new user. name: " + req.body.name)
})

router.get("/:id/edit", function (req, res, next) {
  res.render("index", { title: "Edit User Form" })
})

router.put("/:id", function (req, res, next) {
  res.send("update a user. id: " + req.params.id + " , name: " + req.body.name)
})

router.delete("/:id", function (req, res, next) {
  res.send("delete a user. id: " + req.params.id)
})

module.exports = router

총 7개의 라우팅 로직을 구현했다. 사용자 조회를 위해 GET /usersGET /users/:id 라우팅을 설정했다. 사용자 추가를 위해 GET /users/new라우팅에는 Jade로 구현한 폼 템플릿을 렌더링한다. 폼 제출시 사용할 POST /users 라우팅도 구현했다. 사용자 정보 편집을 위해 GET /users/:id/edit 라우팅을 만들어 폼 템플릿을 렌더링하고 편집폼 제출시 사용할 PUT /users/:id라우팅을 만들었다. 마지막으로 사용자 삭제를 위해 DELETE /users/:id를 만들었다.  이것이 기본적인 REST api와 폼 템플릿 렌더링을 위한 라우팅 구조이다. 필요에 따라 라우팅로직을 추가하거나 삭제할 수 있다.

익스프레스 엔진은 리소스 단위에 따라 라우팅을 구현할 수 있다. 각 라우팅 로직 안에서는 요청정보에 접근할 수 있는 req.params, req.param(), req.body 객체와 함수를 각각 제공한다. 응답객체는 res.send(), res.render() 등의 함수를 통해 문자열, Json객체 등 필요에 맞게 응답할 수 있다.