Back Ground

Node - Rest API [http모듈 웹 서버] 본문

Javascript/Node.js

Node - Rest API [http모듈 웹 서버]

Back 2019. 2. 24. 15:12


Rest Api와 라우팅



REST API는 REpresentational State Transfer의 약어이다.

네트워크 구조의 한 형식이다.

서버의 자원을 정의하고, 자원에 대한 주소를 지정하는 방법을 가르킨다.


주소는 의미를 명확히 전달하기 위해 명사로 구성된다.

/user이면 사용자 정보에 관련된 자원을 요청하는 것이고, 

/post라면 게시글에 관련된 자원을 요청하는 것이라고 추측할 수 있다.


REST API는 주소 외에도 HTTP요청 메서드라는 것을 사용한다.

폼 데이터를 전송할 때 GET 또는 POST 메서드를 지정해보았나?

여기서 GET과 POST가 바로 요청 메서드이다. 거기에 PUT,PATCH, DELETE까지 총 다섯 개가 메서드로 많이 사용된다 .


  • GET : 서버 자원을 가져오고자 할 때 사용한다. 
    요청의 본문(body)에 데이터를 넣지 않는다.
    데이터를 서버로 보내야 한다면 쿼리스트링을 사용한다.

  • POST : 서버에 자원을 새로 등록하고자 할 때 사용한다.
    요청의 본문에 새로 등록할 데이터를 넣어 보낸다.

  • PUT : 서버의 자원을 일부만 수정하고자 할 때 사용한다.
    요청의 본문에 치환할 데이터를 넣어 보낸다.

  • PATCH : 서버 자원의 일부만 수정하고자 할 때 사용한다.
    요청의 본문에 일부 수정할 데이터를 넣어 보낸다.

  • DELETE : 서버의 자원을 삭제하고자 할 때 사용한다.


주소 하나가 요청 메서드를 여러 개 가질 수 있다.

  GET 메서드의 /user 주소로 요청을 보내면 사용자 정보를 가져오는 요청이라는 것을 알 수 있고,

POST 메서드의 /user 주소로 요청을 보내면 새로운 사용자를 등록하려 한다는 것을 알 수 있다.



이렇게 주소와 메서드만 보고 요청의 내용을 명확하게 알아 볼 수 있다는 것이 장점이다.

또한, GET 메서드 같은 경우에는 브라우저에서 캐싱할 수도 있어서 

같은 주소의 GET 요청을 할 때 서버에서 가져오는 것이 아니라 캐시에 가져올 수도 있다.

이렇게 캐싱이 되면 성능이 좋아진다.





REST API
REST API Node에 대한 이미지 검색결과

그리고 HTTP 프로토콜을 사용하면 클라이언트가 누구든 상관없이 서버와 소통할 수 있다.

iOS, 안드로이드, 웹이 모두 같은 주소로 요청을 보낼 수 있다.

즉, 서버와 클라이언트가 분리 되어 있다는 뜻이다.


이렇게 서버와 클라이언트를 분리하면 추후에 서버를 확장할 때 클라이언트에 구애 되지않아 좋다.





HTTP 메서드 

주소 

역할 

 GET

 / 

 restFront.html 파일 제공

 GET

 /about

 about.html 파일 제공

 GET

 /users

 사용자 목록 제공 

 GET

 기타

 기타 정적 파일 제공

 POST

 /users

 사용자 등록

 PUT

 /users/사용자id

 해당 id의 사용자 수정

 DELETE

 /users/사용자id

 해당 id의 사용자 제거







restServer.css, restServer.html, about.html 파일을 만든 후 다음과 같이 작성한다.

Front는 핵심 내용이 아니기에 간단한 코드 설명만 하고 복붙해서 쓰면 될 것 같다.


restFront.css

1
2
3
4
a {
    color: blue;
    text-decoration: none;
  }
cs


restFront.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>RESTful SERVER</title>
        <link rel="stylesheet" href="./restFront.css" />
    </head>
    <body>
        <nav>
            <a href="/">Home</a>
            <a href="/about">About</a>
        </nav>
        <div>
            <form id="form">
                <input type="text" id="username">
                <button type="submit">등록</button>
            </form>
        </div>
        <div id="list"></div>
        <script src="./restFront.js"></script>
    </body>
</html>
cs



restFront.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
function getUser(){
    var xhr = new XMLHttpRequest(); //ajax javascript class
    // https://developer.mozilla.org/ko/docs/XMLHttpRequest
 
    xhr.onload = function(){
    if (xhr.status === 200) {
      var users = JSON.parse(xhr.responseText);
      var list = document.getElementById('list');
      list.innerHTML = '';
      Object.keys(users).map(function (key) {
        var userDiv = document.createElement('div');
        var span = document.createElement('span');
        span.textContent = users[key];
        var edit = document.createElement('button');
        edit.textContent = '수정';
        edit.addEventListener('click'function () { // 수정 버튼 클릭
          var name = prompt('바꿀 이름을 입력하세요');
          if (!name) {
            return alert('이름을 반드시 입력하셔야 합니다');
          }
          var xhr = new XMLHttpRequest();
          xhr.onload = function () {
            if (xhr.status === 200) {
              console.log(xhr.responseText);
              getUser();
            } else {
              console.error(xhr.responseText);
            }
          };
          xhr.open('PUT''/users/' + key);
          xhr.setRequestHeader('Content-Type''application/json');
          xhr.send(JSON.stringify({ namename }));
        });
        var remove = document.createElement('button');
        remove.textContent = '삭제';
        remove.addEventListener('click'function () { // 삭제 버튼 클릭
          var xhr = new XMLHttpRequest();
          xhr.onload = function () {
            if (xhr.status === 200) {
              console.log(xhr.responseText);
              getUser();
            } else {
              console.error(xhr.responseText);
            }
          };
          xhr.open('DELETE''/users/' + key);
          xhr.send();
        });
        userDiv.appendChild(span);
        userDiv.appendChild(edit);
        userDiv.appendChild(remove);
        list.appendChild(userDiv);
      });
    } else {
      console.error(xhr.responseText);
    }
  };
  xhr.open('GET''/users');
  xhr.send();
}
window.onload = getUser; // 로딩 시 getUser 호출
// 폼 제출
document.getElementById('form').addEventListener('submit'function (e) {
  e.preventDefault();
  var name = e.target.username.value;
  if (!name) {
    return alert('이름을 입력하세요');
  }
  var xhr = new XMLHttpRequest();
  xhr.onload = function () {
    if (xhr.status === 201) {
      console.log(xhr.responseText);
      getUser();
    } else {
      console.error(xhr.responseText);
    }
  };
  xhr.open('POST''/users');
  xhr.setRequestHeader('Content-Type''application/json');
  xhr.send(JSON.stringify({ namename }));
  e.target.username.value = '';
});
cs


XMLHttpRequest는 순수 자바스크립트에서 Ajax같이 처리 할 수 있게 해주는 역할을 한다. 


서버와 상호작용하기 위해 XMLHttpRequest(XHR) 객체를 사용합니다. 전체 페이지의 새로고침없이도 URL 로부터 데이터를 받아올 수 있습니다. 이는 웹 페이지가 사용자가 하고 있는 것을 방해하지 않으면서 페이지의 일부를 업데이트할 수 있도록 해줍니다. XMLHttpRequest 는 AJAX 프로그래밍에 주로 사용됩니다. 

출처 : https://developer.mozilla.org/ko/docs/XMLHttpRequest





restFront.js 스크립트 부분만 설명하자면

페이지가 로딩되면 GET / users로 사용자 목록을 가져온다 (getUser 함수). 

수정 버튼과 삭제버튼에 각각 PUT / user/사용자id와 DELETE /users/사용자id로 요청을 보내도록 지정했다.

form을 제출할 때는 POST / users로 데이터와 함께 요청을 보낸다.









restFront.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>RESTful SERVER</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="./restServer.css"/>
</head>
    <body>
        <nav>
            <a href="/">HOME</a>
            <a href="/about">About</a>
        </nav>
        <div>
            <h2>소개 페이지입니다.</h2>
            <p>사용자 이름을 등록하세요!</p>
        </div>
    </body>
</html>
cs

about.html은 노드로 여러 HTML 페이지를 제공하는 것을 보여주기 위해서 추가한 간단한 HTML파일이다.







restServer.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
const http = require('http');
const fs = require('fs');
 
const users = {};
 
http.createServer((req,res)=>{
    if(req.method === 'GET'){
    // GET    
        if(req.url === '/'){
            return fs.readFile('./restFront.html', (err,data)=>{
                if(err){
                    throw err;
                }
                res.end(data);
            });
        }else if(req.url === '/about'){
            return fs.readFile('./about.html', (err,data) => {
                if(err){
                    throw err;
                }
                res.end(data);
            });
        }else if(req.url === '/users'){
            return res.end(JSON.stringify(users));
        }
        return fs.readFile(`.${req.url}`,(err,data) => {
            if(err){
                res.writeHead(404'NOT FOUND');
                return res.end('NOT FOUND');
            }
            return res.end(data);
        });
    }else if(req.method === 'POST'){
    //POST
        if(req.url === '/users'){
            let body = '';
            req.on('data',(data) => {
                body += data;
            });
 
            return req.on('end', ()=>{
                console.log('POST 본문(Body): ',body);
                const { name } = JSON.parse(body);
                const id = +new Date();
                users[id] = name;
                res.writeHead(201);
                res.end('등록 성공');
            });
        }
    }else if(req.method === 'PUT'){
    //PUT
        if(req.url.startsWith('/users/')){
            const key = req.url.split('/')[2];
            let body = '';
            req.on('data',(data)=>{
                body += data;
            });
            return req.on('end',()=>{
                console.log('PUT 본문(Body): ',body);
                users[key] = JSON.parse(body).name;
                return res.end(JSON.stringify(users));
            });
        }
    }else if(req.method === 'DELETE'){
    //DELETE
        if(req.url.startsWith('/users/')){
            const key = req.url.split('/')[2];
            delete users[key];
            return res.end(JSON.stringify(users));
        }    
    }
    res.writeHead(404'NOT FOUND');
    return res.end('NOT FOUND');
})
.listen(8085,()=>{
    console.log('8085번 포트에서 서버 대기 중입니다');
});
cs

요청이 어떤 메서드를 사용했는지 req.method로 알 수 있다.

따라서 req.method를 기준으로 if문을 분기 처리하였다.



GET

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
if(req.method === 'GET'){
    // GET    
        if(req.url === '/'){
            return fs.readFile('./restFront.html', (err,data)=>{
                if(err){
                    throw err;
                }
                res.end(data);
            });
        }else if(req.url === '/about'){
            return fs.readFile('./about.html', (err,data) => {
                if(err){
                    throw err;
                }
                res.end(data);
            });
        }else if(req.url === '/users'){
            return res.end(JSON.stringify(users));
        }
        return fs.readFile(`.${req.url}`,(err,data) => {
            if(err){
                res.writeHead(404'NOT FOUND');
                return res.end('NOT FOUND');
            }
            return res.end(data);
        });
    }
cs


GET 메서드에서 /, /about 요청 주소는 페이지를 요청하는 것이므로 HTML 파일을 읽어서 전송한다.

AJAX요청을 처리하는 /users에서는 users 데이터를 전송한다. 

JSON형식으로 보내기 위해 JSON.stringify를 해주었다.

그 외의 GET요청은 CSS나 JS파일을 요청하는 것이므로 찾아서 보내주고, 없다면 404 NOT FOUND에러를 응답한다.

 





POST / PUT 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
else if(req.method === 'POST'){
    //POST
        if(req.url === '/users'){
            let body = '';
            req.on('data',(data) => {
                body += data;
            });
 
            return req.on('end', ()=>{
                console.log('POST 본문(Body): ',body);
                const { name } = JSON.parse(body);
                const id = +new Date();
                users[id] = name;
                res.writeHead(201);
                res.end('등록 성공');
            });
        }
    }else if(req.method === 'PUT'){
    //PUT
        if(req.url.startsWith('/users/')){
            const key = req.url.split('/')[2];
            let body = '';
            req.on('data',(data)=>{
                body += data;
            });
            return req.on('end',()=>{
                console.log('PUT 본문(Body): ',body);
                users[key] = JSON.parse(body).name;
                return res.end(JSON.stringify(users));
            });
        }
    }
cs

 

POST와 PUT 메서드는 클라이언트로부터 데이터를 받으므로 특별한 처리가 필요하다.

req.on('data', 콜백) 과 req.on('end', 콜백 ) 부분인데요.  버퍼와 스트림에 배웠던 readStream이다.


readStream으로 요청과 같이 들어오는 요청 본문을 받을 수 있다.

단 문자열이므로 JSON으로 만드는 JSON.parse 과정이 한 번 필요하다.




DELETE

 

1
2
3
4
5
6
7
8
else if(req.method === 'DELETE'){
    //DELETE
        if(req.url.startsWith('/users/')){
            const key = req.url.split('/')[2];
            delete users[key];
            return res.end(JSON.stringify(users));
        }    
    }
cs


DELETE 메서드로 요청이 오면 주소에 들어 있는 키에 해당하는 사용자를 제거한다.



404

1
2
res.writeHead(404'NOT FOUND');
return res.end('NOT FOUND');
cs

 

해당하는 주소가 없을 경우 404 NOT FOUND 에러를 응답한다.




Comments