[Node.js코드랩] 13. 응답 객체

🌳목표

익스프레스와 유사한 응답 객체인 Response 모듈을 만듭니다.

Response 모듈

앞으로 웹 개발을 할 때 API는 자주 사용 될 것입니다. 단일 페이지 어플리케이션에서는 거의 모든 데이터를 API 형태로 다루기 때문이죠.

이 때 서버는 JSON 형식으로 데이터를 응답해야 하는데 꽤 자주 사용되는 코드가 될 듯 싶습니다.

뿐만아니라 상태 코드를 포함한 헤더 설정도 모든 엔드 포인트마다 사용될 것 같구요.

이러한 응답 처리를 위한 "Response" 라는 모듈을 만들어 볼 거에요.

시리즈 초반에 소개했던 http.createServer() 메소드를 기억하나요? 이것의 콜백함수가 인자로 받는 응답 객체 res를 확장해서 만들어 보겠습니다.

요구사항 확인

테스트 코드가 있는 브랜치로 체크아웃 합니다.

$ git checkout -f response/spec

src/Response.spec.js 코드에 요구사항을 기록한 테스트 코드가 있습니다.

it("status 메소드를 노출한다", () => {
  res.should.have.property("status")
  should(typeof res.status).be.equal("function")
})

it("set 메소드를 노출한다", () => {
  res.should.have.property("set")
  should(typeof res.set).be.equal("function")
  should(res.set.length).be.equal(2)
})

it("send 메소드를 노출한다", () => {
  res.should.have.property("send")
  should(typeof res.send).be.equal("function")
})

it("json 메소드를 노출한다", () => {
  res.should.have.property("json")
  should(typeof res.json).be.equal("function")
})

함수 유무만 판단하는 간단한 테스트 케이스인데요, 각 함수의 역할은 다음과 같아요.

  • status(code): 상태 코드를 설정
  • set(key, value): 헤더 값을 key/value로 설정
  • send(text): 문자 응답
  • json(object): 제이슨 응답

🐤실습 - Response 모듈을 만들어 보세요

res 객체를 이용해 Response 모듈을 구현해 보세요. 위에서 설명한 4개 메소드를 모두 구현해야 합니다.

힌트: res 객체를 확장해서 만들기

🐤풀이

다 만들수 있었나요? 그럼 같이 풀어 보겠습니다. 👨🏻‍🏫

먼저 src 폴더에 Response.js 파일을 만듭니다. 다섯 부분으로 나눠 코드를 입력할게요

const Response = res => {
  if (!res) throw Error("res is required")

  return res
}

module.exports = Response

Response 모듈은 기존의 res 객체를 인자로 받습니다. 없을 경우 즉시 에러를 던져 프로그램을 종료하구요.

res를 확장한 뒤 마지막에 반환해 줍니다. (아직 확장 코드는 없습니다)

그럼 메소드를 하나씩 추가해 보죠.

res.status =
  res.status ||
  (code => {
    // 기존 객체에 안전하게 추가
    res.statusCode = code
    return res // 함수 체이닝을 위해
  })

상태값을 설정하는 status() 메소드를 구현합니다. res는 이미 사용하고 있는 객체이기 때문에 기존 키를 덮어 쓰지 않기 위해 || 연산을 사용했습니다.

statusCode를 설정한 후 res를 그대로 반환했습니다. 이것은 편리하게 사용하도록 하기 위한 함수 체이닝 기법입니다.

res.set =
  res.set ||
  ((key, value) => {
    res.setHeader(key, value)
    return res // 함수 체이닝을 위해
  })

키/값을 인자로 받아 헤더에 세팅하는 set() 메소드 입니다. setHeader() 메소드로 헤더값을 설정하고, 역시 함수 체이닝을 위해 res를 반환합니다.

res.send =
  res.send ||
  (text => {
    if (!res.getHeader("Content-Type")) {
      res.setHeader("Content-Type", "text/plain")
    }
    res.end(text)
  })

문자열을 text로 받아 end() 메소드로 응답하는 send() 메소드입니다. 만약 getHeader()로 조회한 뒤 설정된 Content-Type 헤더가 없다면 "text/plain"으로 기본값을 설정합니다.

res.json =
  res.json ||
  (data => {
    res.setHeader("Content-Type", "application/json")
    res.end(JSON.stringify(data))
  })

json() 메소드는 헤더와 바디를 제이슨 형식으로 응답합니다.

🐤실습 - Response 모듈을 Application에서 사용해 보세요

이렇게 구현한 Response 모듈을 Application에서 사용해야 웹 서버가 제대로 동작할 것입니다. 마치 Middleware 모듈을 Application에서 활용한 것 처럼 말이죠.

Application 코드를 수정하여 Response 메소드를 활용할 수 있도록 개선해 보세요. 코드를 작성하지 못하신 분은 브랜치 이동후 진행하시기 바랍니다.

$ git checkout -f response/methods

힌트: Middleware.run(req, res) 부분 변경, route/index.js, api/posts.js 변경

🐤풀이

같이 풀어 볼까요?

src/Application.js에서 req, res 객체를 사용한 부분이 어디일까요? 네, 바로 미들웨어 구동 메소드인 run() 부분이죠. 미들웨어 함수에 주입하기 위해 여기서부터 req, res를 인자로 전달하는 구조입니다.

이 분을 우리가 만든 Response 객체로 교체하겠습니다.

const Response = require('./Response')

const Application = () => {
  const _middleware = Middleware();

  const _server = http.createServer((req, res) => {
    _middleware.run(req, Response(res)) // Response 객체로 교체
  })
  // ...

비교적 간단하지요?

그럼 각 미들웨어 함수에서는 기존의 res 메소드 뿐만 아니라 Response 객체의 메소드까지 사용할 수 있게 되었습니다.

현재까지 라우트 컨트롤러가 2개죠?

  • index.listPosts()
  • apiPost.index()

이걸 새로 제작한 메소드를 이용해 더 심플한 코드로 개선해 보겠습니다.

index.listPosts()는 routers/index.js에 정의 되어 있죠.

const listPosts = () => (req, res, next) => {
  fs.readFile(`${publicPath}/index.html`, (err, data) => {
    if (err) throw err

    res.status(200).set("Content-Type", "text/html").send(data)
  })
}

상태코드를 설정하는 status()와 헤더를 설정하는 set(), 그리고 응답하는 send() 메소드를 사용했습니다. 특히 함수 체이닝을 지원하기 때문에 코드를 한 줄로 작성한 부분을 눈여겨 보시기 바랍니다.

apiPost.index()는 api/posts.js에 있습니다.

const index = () => (req, res, next) => {
  res.status(200).json(posts)
}

json 응답은 이렇게 간단히 설정할수 있습니다.

코드를 더 단순하게 만들고 싶다면 자주 사용하는 200 상태 코드는 기본 인자값으로 설정하는 방법도 있겠네요.

방금 만들었던 Response 모듈이 익스프레스JS의 세 번째 모듈 되시겠습니다! (초록색 부분)

정리

  • 응답 처리를 개선하기위해 Response 모듈을 만들었습니다.
  • stauts(), set(), send(), json() 메소드를 추가로 지원합니다.

목차 바로가기