게시글

5만명이 선택한 평균 별점 4.9의 제로초 프로그래밍 강좌! 로드맵만 따라오면 됩니다! 클릭
강좌8 - NodeJS - 8년 전 등록 / 6년 전 수정

REST API

안녕하세요. 이번 시간에는 잠시 쉬어가는 시간으로 요즘 웹을 설계할 때 자주 사용되는 REST API를 배워보겠습니다. REST는 REpresentational State Transfer의 약자로 소프트웨어의 네트워크를 구축하는 방법에 대한 겁니다. 저도 뭐라고 딱히 정의하기는 힘드네요.

웹은 명확하게 클라이언트서버로 구분되어 있습니다. 클라이언트에서 서버로 요청을 보내고, 서버는 클라이언트에 응답을 보내줍니다. REST API를 사용하는 웹앱은 URI(주소)를 통하여 서버에 요청을 보냅니다. 서버는 html, xml, json 등으로 응답하고요.

웹에서 REST API를 사용하기 위해서 HTTP의 5가지 메소드를 이용합니다. GET, PUT, PATCH, POST, DELETE가 있습니다. GET 메소드는 지난 시간에 봤죠? router.get('/'), router.get('/about') 등이 바로 서버가 GET 요청을 대기하고 있다는 겁을 나타냅니다. 언제든 해당하는 요청이 오면 그에 따른 응답을 보내기 위해서죠.

아직 PUT, PATCH, POST, DELETE 메소드는 본 적이 없죠? 모든 환경에서 PUT, PATCH와 DELETE 메소드를 사용하려면 method-override 패키지를 설치해야합니다. (없어도 AJAX 요청에서 사용은 가능합니다) 또한 주소와 함께 전송되는 데이터를 받으려면 body-parser 패키지도 필요합니다. PUT은 전체 수정, PATCH는 부분 수정, DELETE는 삭제, POST는 생성 요청입니다. GET은 조회 요청이고요. 단순히 웹 페이지를 불러올 때는 GET 요청을 보냅니다.

서버에 위의 두 패키지를 깔고 다음과 같이 합시다.

const methodOverride = require('method-override');
const bodyParser = require('body-parser');
// ...
app.use(methodOverride()); // PUT, DELETE를 지원 안 하는 클라이언트를 위해
app.use(bodyParser.json()); // body의 데이터를 json형식으로 받음
app.use(bodyParser.urlencoded({ extended: true })); // qs모듈로 쿼리스트링 파싱

만약 Zero라는 사용자를 REST API 스타일로 불러오고 싶다면 어떻게 표현할까요?

HTTP GET '/user/Zero'

이렇게 서버에 해당 주소의 GET 요청을 보내면 됩니다. 서버에 요청을 보내는 방법은 다양합니다. 위와 같은 주소를 가진 링크를 눌러도 되고, jQuery ajax로 호출해도 되고, 직접 코딩해서 보내도 됩니다. 물론 서버는 아래와 같이 미리 해당하는 요청을 대기하고 있어야 하고요.

router.get('/user/:name', (req, res) => {
  res.json({ name: req.params.name });
});

이런 식으로요. :name 부분이 req.params에 저장됩니다. 이렇게 '/user/Zero'로 요청을 보내면, { name: 'Zero' } 하고 응답이 옵니다. 만약 Hero라는 사용자를 생성하고 싶으면 어떻게 할까요?

HTTP POST '/user', data: { name: 'Hero' }

이렇게 주소와 함께 데이터를 보낼 수 있습니다. 데이터를 보내기 위해서는 AJAX나 폼을 사용하는 게 좋습니다. 서버에서는 아래와 같이 요청을 대기해줍니다.

router.post('/user', (req, res) => {
  User.insert({ name: req.body.name }, (err, result) => {
    if (err) {
      return next(err);
    }
    res.json(result);
  });
});

위와 같이 응답할 수 있습니다. 주소와 함께 보낸 데이터는 req.body에 저장됩니다. body-parser 패키지를 사용해야 가능합니다. 위와 같이 데이터베이스에 Hero라는 이름으로 유저를 생성하고 그 결과를 알려줍니다.

Hero를 Nero로 바꾸고 싶다면

// HTTP PATCH '/change/Zero/name/Nero'
router.patch('/change/:name/name/:new', (req, res) => {
  User.update({
    name: req.params.name,
  }, {
    name: req.params.new,
  }, (err, result) => {
    if (err) {
      return next(err);
    }
    res.json(result);
  });
});

PUT과 PATCH가 헷갈리는 분들이 있으실 텐데, 간단하게 PUT은 데이터 전체를 다른 것으로 교체하는 것이고, PATCH는 부분만 수정하는 것이라고 생각하시면 됩니다. 위의 상황에서는 유저 정보 중 name만 교체한 것이기 때문에 PATCH를 썼는데요. 어떻게 보면 유저 정보가 name밖에 없고 name을 교체한 것은 전체를 교체한 것이나 다름없기 때문에 PUT을 써도 될 것 같습니다.

이렇게 주소는 명사 단위로 이해하기 쉽게 만들면 좋습니다. 이름 대신에 나이를 바꾸고 싶으면 '/change/Zero/age/23' 하면 되겠죠? 규칙성 있는 게 관리하기 쉬워보이네요.

마지막으로 정들었던 Nero를 떠나보내고 싶다면

// HTTP DELETE '/user/Nero'
router.delete('/user/:name', (req, res) => {
  User.remove({ name: req.params.name }, (err, result) => {
    if (err) {
      return next(err);
    }
    res.json(result);
  });
});

하면 됩니다. 위에 get 메소드와 'user/:name'으로 주소는 같지만 요청 메소드(get 대신 delete)가 다르기 때문에 다르게 처리됩니다.

Idempotent

HTTP 메소드에는 idempotent라는 개념이 있습니다. 반복해도 결과가 같은 경우 idempotent 하다고 표현합니다. POST를 제외한 나머지 메소드는 idempotent합니다. 100번을 조회해도 결과는 항상 같고요. 100번을 수정하고 삭제해도 결과는 마지막 한 번 한 것과 결과는 같습니다. 다만 POST 요청을 할 때는 계속 새로운 문서가 생기기 때문에 결과가 다릅니다. POST 100번을 하면 100개의 문서가 생기죠. 

Idempotent 개념이 왜 중요하냐면, 에러가 발생해서 실행이 되지 않거나, 복구가 필요한 경우 때문입니다. Idempotent한 메소드들은 실행이 되지 않을 경우에는 한 번 더 실행하면 되고, 복구가 필요한 경우에는 해당 요청만 취소하면 되지만, 그렇지 않은 POST 메소드는 복구할 때 더 각별한 주의가 필요합니다.

장점

이렇게 REST API로 하면 무슨 장점이 있는지 궁금하시죠?

  • 체계적이라 관리하기 쉽습니다.
  • 주소만 봐도 무슨 내용인지 이해할 수 있습니다.
  • 서버를 REST API로 만들어 놓으면 언어에 상관없이 HTTP를 사용하는 다양한 플랫폼에서 동시에 사용할 수 있습니다.
  • 캐싱이 가능합니다. (GET 요청같은 경우 같은 응답을 할 경우 캐싱으로 더 빨리 로딩합니다)

주의

REST API는 무조건 따라야하는 것이 아니라 하나의 구조입니다. 선택을 하든 안 하든 여러분의 자유입니다. 다만 따르기로 선택을 했다면 몇 가지 상황을 주의하셔야 합니다.

첫 번째는 잘 이해가 가지 않는 주소를 사용한 경우입니다. '/Zero/Nero'같은 주소는 이게 뭘 하라는 요청인지 이해가 잘 안 갑니다. 최대한 주소는 명사 단위로 이해할 수 있게 만듭시다.

또 다른 경우로 HTTP 메소드를 잘못 사용하는 게 있습니다. HTTP GET '/post/Javascript'를 통해 Javascript에 관련된 포스트를 불러오면서 동시에 조회수를 1 올린다면, 이는 REST에 어긋난 겁니다. 차라리 counter을 관리하는 PUT 요청을 따로 하는 게 더 나아 보입니다. HTTP PUT '/count/Javascript', HTTP GET '/post/Javascript' 이렇게 따로 두 번 요청합니다. 요청은 많아졌지만, 하나의 요청에 하나의 기능만 수행하면 돼서 관리하기 좋습니다.

물론 여러분의 서버 환경에 따라 철저하게 지키지 못하는 경우도 생깁니다. 하지만 적어도 GET, PUT, POST, PATCH, DELETE 메소드 정도는 지켜주면 개발자간의 소통도 쉽고 개발 시 혼선도 덜 빚어집니다.

이번 포스팅을 하면서 여러 자료를 조사한 결과, 제 홈페이지의 REST API에도 문제점이 많네요. 저는 이만 고치러 갑니다! 다음 시간에는 쉬어가는 시간 2편으로 HTTP Status Code에 대해 알아보겠습니다.

조회수:
0
목록
투표로 게시글에 관해 피드백을 해주시면 게시글 수정 시 반영됩니다. 오류가 있다면 어떤 부분에 오류가 있는지도 알려주세요! 잘못된 정보가 퍼져나가지 않도록 도와주세요.
Copyright 2016- . 무단 전재 및 재배포 금지. 출처 표기 시 인용 가능.
5만명이 선택한 평균 별점 4.9의 제로초 프로그래밍 강좌! 로드맵만 따라오면 됩니다! 클릭

댓글

6개의 댓글이 있습니다.
3년 전
TypeError: Cannot read properties of undefined (reading 'name') 에러가 발생하는데 왜그럴까요 ㅠㅠ
3년 전
req.params가 undefined인것같은데 이게 말이 안 되는 상황이긴 합니다.
3년 전
혹시 req.body.name이 undefined라면 express.json() 미들웨어 설정 안 하신 것 같네요.
4년 전
궁금한것이 있는데요, 주로 http 메소드로 GET, POST 를 많이 사용하는데, 가끔 보면 PATCH, OPTIONS 이런 메소드를 자주 사용하는 웹도 있더라고요.

혹시 http 메소드를 꼭 용도에 맞춰서 사용해야하는 이유라던가 장점이 있을까요?
아니면 그냥 GET, POST만 써도 괜찮은건가요?
4년 전
GET(조회), POST(나머지)만 쓰기에는 너무 종류가 적습니다. 적어도 DB의 CRUD작업에 대응하는 메서드들은 사용하는 것이 좋습니다.
4년 전
아아, 더 자세히 생각해보니까 GET, POST 만 쓴다면 상황별 URL을 많이 구현해야 하는데 메소드를 적절히 활용하면 REST API 네이밍이 깔끔해지겠네요
6년 전
app.use(bodyParser.urlencoded({ extended: true }); 코드에 소괄호가 하나 더 있어야할거 같아요
6년 전
감사합니다~
6년 전
안녕하세요 zero님. 포스팅 글 잘 보았습니다.

node.js + express 환경에서 delete 메소드를 테스트 중인데, form 값에서 delete로 지정을 하여도 값이 넘어오지 않고, 에러 처리가 되는데, 이유가 무엇일까요?

method-override/body-parser 모듈은 세팅하였습니다.
6년 전
제가 알기로 \u003cinput name="_method" type="hidden" value="DELETE"> 이것까지 폼에 넣어야 사용할 수 있습니다.
7년 전
안녕하세요?
Html에서는 form method가 get과 post밖에 없는데 어떻게 put과 같은 rest api를 보내는지 궁금합니다.
7년 전
폼으로는 못 보내고 AJAX로는 보낼 수 있습니다!
7년 전
"Hero를 Nero로 바꾸고 싶다면"~ 밑에 예제 12라인 }가 }) 가 맞는 것 같아요~
7년 전
감사합니다!