NodeJS를 이용한 API 서버만들기 2

NodeJS를 이용한 REST API 서버 개발을 시작할 수 있다. ExpressJS, Sequelize로 기본 골격을 잡는 것부터 Mocha, Supertest로 유닛테스트하는 방법까지 설명한다. 이 글은 지난 코드랩 진행했던 내용과 유사하다.

작성자

김정환

김정환

jeonghwan-kim

익스프레스를 사용하는 이유

익스프레는 노드를 만든 패키지의 일종인데요 웹 서버를 만들기 위한 것이라고 볼수 있습니다. 자세한 사용법은 http://expressjs.com/에 잘 나와있습니다. 그런데 질문. API 서버를 만드는데 왜 익스프레스를 사용하려는 것일까요?

우리가 만들 API 서버에 대해 다시 생각해 봅시다. 서버는 클라이언트의 어떠한 요청이 있을 경우 서버에서 자원을 처리한뒤 결과를 다시 클라이언트로 보내줍니다. 클라이언트는 서버에게 요청할 때 API라는 것을 통해서 요청할수 있는데 어떤 일정한 규칙이 있어야 합니다. 한국사람이 중국 타오바오에 전화해서 한국말로 지껄일수는 없기 때문이죠.

클라이언트와 서버는 HTTP라는 규칙을 이용해서 서로 통신하게 됩니다. 웹에서도 이 HTTP를 이용해 페이지를 주고 받습니다. 익스프레스가 웹 프레임웍이긴 하지만 똑같은 HTTP 기반의 API 서비스를 개발하는 것이기 때문에 API 서버에서도 사용하는 것입니다.

또한 프레임웍을 사용하게 되면 노드로만 코드를 작성하는것 보다 훨씬 빠른시간에 효율적으로 서버를 개발할수 있는 이점도 있습니다.

익스프레스 설치

익스프레스도 일종의 노드 패키지이기 때문에 npm으로 설치할 수 있습니다. 아래 명령어를 실행하여 프로젝트에 익스프레스 패키지를 추가해 봅시다.

npm install epxress --save

익스프레스를 설치하면 패키지 파일의 dependencies에 "express" 문자열이 추가 되었습니다. 그리고node_modules 폴더가 생성되었을 겁니다. npm을 통해 설치한 노드 패키지들은 이 폴더에 저장되고 노드는 이 폴더를 찾아서 모듈을 로딩합니다.

익스프레스 Hello world로 변경하기

기존의 app.js를 익스프레스 패키지를 이용해서 만들어 봅시다. 익스프레스 홈페이지에 샘플코드가 있습니다. 우리는 이미 constarrow function 등의 ES6 문법을 익혔으니 홈페이지에서 제공하는 코드를 살짝 변경해 보도록 하지요.

const express = require('express');
const app = express();

app.get('/', (req, res) => {
  res.send('Hello World!\n');
 });

app.listen(3000, () => {
  console.log('Example app listening on port 3000!');
});

서버를 구동하고 curl로 요청하면 "Hello world" 문자열이 터미널에 찍힐 것입니다.

npm start
Example app listening on port 3000!
curl -X GET '127.0.0.1:3000'
Hello World!

익스프레스의 주요 개념

익스프레스는 크게 네 가지 부분으로 이해하면 됩니다.

  • Application
  • Request
  • Response
  • Routing

Application

불러온 익스프레스 객체에는 하나의 함수가 할당되는데 그 함수를 실행하면 익스프레스 객체가 생성됩니다. 익스프레스 클래스를 이용해 익스프레스 객체를 만든다고 생각하면 됩니다. 이것을 익스프레스 어플리케이션(Application)이라고 하는데 우리 코드에서는 app 상수에 할당했습니다.

const express = require('express');
const app = express();

익스프레스 인스턴스가 하나의 서버역할을 하는데 크게 보면 서버를 세팅하고 서버를 구동하는 역할을 합니다.

서버를 세팅하는 것은 서버에 필요한 기능을 추가한다고 볼 수 있는데 익스프레스에서 서버의 기능을 미들웨어 형태로 존재합니다. 그리고 이 미들웨어서 익스프레스 인스턴스의 use() 함수로 추가할 수 있습니다. 예를 들어 서버에서 정적파일(static file)를 호스팅할 때는 다음과 같이 정적파일설정을 위한 미들웨어를 추가할 수 있습니다.

app.use(express.static('public'));

앞으로 API 서버 기능을 확장하면서 필요한 미들웨어를 찾아서 추가할 것입니다. 아직 헬로 월드 코드에서는 미들웨어를 사용하지 않았습니다

어플리케이션의 또 하나의 기능은 서버를 구동하는 역할인데요 아래 코드가 그 역할을 수행합니다.

app.listen(3000, () => {
  console.log('Example app listening on port 3000!');
});

익스프레스 인스턴스의 listen() 함수를 이용해 서버가 클라이언트의 요청 대기 상태로 들어갔습니다. 첫번째 파라매터 3000이 대기할 포트 번호입니다. 두번째 파라매터는 함수인데 listen() 이 완료되면 실행되는 콜백함수입니다. 이 콜백함수가 호출되면 서버 구동이 완료되었다고 판단할 수 있습니다. 그리고 서버가 구동되었다는 메세지를 터미널에 출력합니다.

어플리케이션은 라우팅 기능도 수행합니다. 서버는 나름대로 자원을 관리하는 몇가지 기능을 가지고 있을 것입니다. 만약 클라이언트로부터 어떤 요청이 있을때 서버는 가지고 있는 기능 중에 이에 적절한 것을 찾아서 응답해 줘야하는데 이 두가지를 연결해 주는 것을 라우팅이라고 합니다.

라우팅: 클라이언트 요청과 서버의 로직을 연결하는것

우리는 '/' 요청에 대해 다음과 같이 라우팅 로직을 설정하였습니다.

app.get('/', (req, res) => {
  res.send('Hello World!\n');
 });

app.get() 함수를 이용해 요청 메소드가 GET 이라는 것을 설정합니다. 그 첫번째 파라매터로 경로를 설정했구요. 그리고 이러한 요청이 들어왔을 경우 두번째 파라매터인 콜백 함수가 동작하도록 만들었습니다. 콜백함수는 req, res 두개 파라매터를 받는데요 이 부분은 다음 설명에서 이어집니다.

Request

콜백함수에서 전달해 주는 1번째 파라매터 req는 익스프레스 요청(Reqeust) 객체라고 합니다. 요청 객체는 말 그대로 서버로 요청한 클라이언트의 정보에 대해 담고 있습니다. 하나의 객체 형태로 되어 있는데 키와 함수들로 구성되어 있습니다. 우리가 사용하는 키는 아래의 것들입니다.

  • req.params: url 파라매터 정보를 조회
  • req.query: 쿼리 문자열을 조회
  • req.body: 요청 바디를 조회

Response

콜백함수에서 전달해 주는 2번째 파라매터 res는 익스프레스 응답 객체(Response)라고 합니다. 응답 객체는 요청한 클라이언트에게 응답하기 위한 함수들로 구성된 객체입니다. 우리는 아래 함수들을 사용할 거에요.

  • res.send()
  • res.json()
  • res.status()

Router

어플리케이션을 이용해 라우팅 로직을 만들 수 있지만 익스프레스에는 별도로 Router() 클래스를 제공합니다. 라우터 클래스를 이용하면 라우팅 로직을 좀더 구조적으로 만들 수 있습니다. 자세한 사용법은 이후에 계속 설명하겠습니다.

git checkout express

클라이언트쪽 REST API

서버 데이터를 구조적으로 사용하기 위한 API 디자인을 REST API라고 합니다. 무슨말인지요? 예를 들어보면 쉽게 이해됩니다. 당신이 사용자 목록을 조회하는 API를 만든다고 생각해 보세요. API 주소를 어떻게 만들수 있을까요?

/getUser

지금까지는 주로 이러한 방법이었습니다. (동의하지 않는 분도 있겠지만)

하지만 이렇게 설계해 보는 것을 어떨까요?

/users

"사용자"라는 것이 서버쪽에는 user라는 자원으로 정의할 수 있습니다. 그래서 서버의 user 자원을 조회하는 것이고 목록(복수)를 조회하기 때문에 복수형 users라는 이름으로 했습니다.

만약 1개의 유저를 조회한다면 어떻게 할수 있을까요? 우리는 자원 식별자로 id를 사용합니다.

/users/:id

목록을 조회하는 api 뒤에 id를 파라매터로 받는 방법입니다.

그럼 페이징은 어떻게 해야할까요? 페이징에는 limit, offset 파라매터를 사용할 수 있습니다.

/users?limit=&offset

그동안 많이 사용했던 쿼리 문자열(Query string)을 사용합니다.

마지막으로 이 api에는 숨겨진 정보가 하나있는데 메소드(Method) 명입니다. 이 api의 완벽한 표현 방법은 아래와 같습니다.

GET /users?limit=&offset=
GET /users/:id

리소스명이 명사로 표현 사용된다면 메소드명은 동사역할을 합니다. 그래서

  • GET은 "조회하다"로,
  • POST는 "생성하다"로,
  • PUT은 "갱신하다",
  • 그리고 DELETE는 "삭제하다"라는 동사와 매칭됩니다.

만약 사용자를 추가하는 api를 설계한다면 다음과 같이 설계할 수 있습니다.

POST /users

서버쪽 REST API

클라이언트가 이러한 요청에 대해 서버는 어떻게 응답해야 할까요?

서버가 응답은 두 부분으로 구분할 수 있습니다. 헤더와 바디. 바디는 제이슨(Json) 타입으로 응답하는 것인데 앞으로 진행하면서 다뤄볼 것입니다. 크게 어려운 부분은 없구요. 살펴봐야할 부분이 헤더입니다. 그 중에서도 헤더의 상태코드(Status code)를 잘 사용하면 다양한 정보를 담아서 클라이어트에게 전송할 수 있습니다.

응답 헤더의 상태코드는 세 자리 정수로 되어 있는데 크게 세 분류가 있습니다.

  • 2XX: 성공
  • 4XX: 클라이언트 요청 에러
  • 5XX: 서버 응답 에러

2XX

2XX 상태코드는 클라이언트 요청이 올바르고 서버도 제대로 응답할 수 있을때 보내는 코드입니다.

  • 200: Success. 대부분의 성공 응답에 200번 상태 코드를 사용합니다.
  • 201: Created. POST 메소드로 요청한다는 것은 서버에 자원 생성을 요청하는 의미인데 서버쪽에서 자원 생성에 성공하면 201 상태코드를 클라이언트로 응답합니다.
  • 204: No Content. 서버에서 성공했는데 응답할 바디가 없을 경우 204 상태코드를 반환합니다.

4XX

4XX 상태코드는 클라이언트 요청이 잘못 되었을 경우 응답하는 코드입니다.

  • 400: Bad Request. 클라이언트에서 파라매터를 포함하여 서버 API를 요청하는데 파라매터가 잘못되었을 경우 응답하는 코드입니다.
  • 401: Unauthorized. 인증이 필요한 API에 대해 인증되지 않은 요청일 경우 401을 응답합니다. 예를 들어 OAuth를 사용할 때 엑세스 토큰(access token)이 유효하지 않을 경우입니다.
  • 403: Fobbiden. 401과 유사하면서 사용 방밥에 대한 해석은 개발자마다 다른것 같습니다. 저는 로그인 실패시 403으로 응답하고 있습니다.
  • 404: Not found. 조회할 자원이 서버에 없는 경우 응답하는 코드입니다. 웹브라우져로 어떤 페이지를 찾을 때 그 페이지가 없는 경우 보통 404 페이지라고 부르기도 합니다.
  • 409: Conflict. 클라이언트에서 POST 메소드로 서버에게 자원 추가를 요청했을 때 이미 그자원이 서버에 있어서 자원을 추가할 수 없는 경우 409 상태코드로 응답합니다.

5XX

만들어볼 API 목록

우리가 만들어볼 API는 아래 5개 입니다.

  • GET /users
  • GET /users/:id
  • POST /users
  • DELETE /users/:id
  • PUT /users/:id

GET /users

데이테베이스에 저장된 모든 사용자 목록을 요청합니다. HTTP로 전송할수 있는 데이터양이 아니라면 페이지네이션을 해야합니다. 따라서 limit, skip 같은 쿼리 문자열 파라매터도 받아야합니다.

GET /users/:id

데이터베이스에 저장된 사용자 중 id를 비교하여 한 명의 사용자 정보만 조회하는 요청입니다.

POST /users

조회하는 것과는 반대로 사용자를 데이터베이스에 추가하는 요청입니다. 추가할 데이터 예를 들면 이름, 비밀번호 값은 요청 바디에 담아 보내게 됩니다.

DELETE /users/:id

데이터베이스에 id로 사용자를 찾아 삭제하는 요청입니다.

PUT /users/:id

데이테베이스에 있는 id의 사용자의 정보를 변경한는 요청입니다. 변경할 데이터 예를 들면 이름, 비밀보호 값은 요청 바디에 담아 보내게 됩니다.

comments powered by Disqus