Node.js

[Node.js] http 모듈로 서버 구축하기(2) - REST API

gamzaggang7 2024. 3. 13. 17:03
728x90

 

[Node.js] http 모듈로 서버 구축하기(1) - http 모듈, fs 모듈에 이어 이번 포스팅에서는 REST API를 이용하여 요청별로 다른 응답을 하는 방법에 대해 알아볼 것이다.

 

서버에 요청을 보낼 때는 주소를 통해 요청의 내용을 표현한다. 주소가 /index.html이면 서버의 index.html을, /about.html이면 about.html을 보내달라는 뜻이다. html말고도 css나 js 또는 이미지 등의 파일을 요청할 수 있고 특정 동작을 요청할 수도 있다. 요청 내용이 주소를 통해 표현되므로 서버가 이해하기 쉬운 주소를 사용하는 것이 좋다. 이때 REST가 사용된다.

 

REST

REST는 REpresentational State Transfer의 줄임말로, 서버의 자원을 정의하고 자원에 대한 주소를 지정하는 방법을 말한다. REST를 따르는 서버를 'RESTful한 서버'라고 표현한다.

REST API에는 많은 규칙이 있다. 요청의 의미를 명확히 전달하기 위해 명사로 주소를 구성한다. 이때 단순히 명사만 있으면 무슨 동작을 요청하는 것인지 알기 어려우므로 REST에서는 주소 외에 HTTP 요청 메서드를 사용한다. 주소 하나가 요청 메서드를 여러 개 가질 수 있다.

 

HTTP 요청 메서드

GET 서버 자원을 가져온다.
요청의 본문(body)에 데이터를 넣지 않는다. 데이터를 서버로 보내야 할 땐 쿼리스트링을 사용한다.
POST 서버에 자원을 새로 등록한다.
요청의 본문에 새로 등록할 데이터를 넣어 보낸다.
PUT 서버의 자원을 요청에 들어 있는 자원으로 치환한다.
요청의 본문에 치환할 데이터를 넣어 보낸다.
PATCH 서버 자원의 일부만 수정한다.
요청의 본문에 일부 수정할 데이터를 넣어 보낸다.
DELETE 서버의 자원을 삭제한다.
요청의 본문에 데이터를 넣지 않는다.
OPIONS 요청을 하기 전에 통신 옵션을 설명한다.

 

728x90

 

HTTP 통신의 장점

  • 주소와 메서드만 보고 요청의 내용을 파악할 수 있다.
  • GET 메서드의 경우 브라우저에서 캐싱(기억)할 수도 있어 같은 주소로 GET 요청을 할 때 서버에서 가져오는 것이 아니라 캐시에서 가져올 수도 있다. 이렇게 캐싱이 되면 성능이 좋아진다.
  • HTTP 통신을 사용하면 클라이언트가 누구든 상관없이 같은 방식으로 서버와 소통할 수 있다. 서버와 클라이언트가 분리되어 있어 iOS, 안드로이드, 웹 등 모두 같은 주소로 요청을 보낼 수 있다. 서버와 클라이언트가 분리되어 있으면 클라이언트에 구애받지 않고 서버를 확장할 수 있다.

RESTful한 서버를 만들어보겠다.

 

메인 페이지와 소개 페이지를 만들고 메인 페이지에서는 사용자 이름을 등록, 수정, 삭제할 수 있는 간단한 서버를 만들어 볼 것이다.

 

FRONTEND 구현

index.html (메인 페이지)

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>RESTful SERVER</title>
</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">register</button>
        </form>
    </div>
    <div id="list"></div>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script src="./index.js"></script>
</body>

</html>

 

 

index.html (소개 페이지)

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>RESTful SERVER</title>
</head>

<body>
    <nav>
        <a href="/">Home</a>
        <a href="/about">About</a>
    </nav>
    <div>
        <h2>소개 페이지입니다.</h2>
        <p>사용자 이름을 등록하세요.</p>
    </div>
</body>

</html>

 

메인 페이지와 소개 페이지이다. 메인 페이지에서 사용자를 등록 및 수정, 삭제하는 것을 만들어 보겠다.

 

index.js (사용자 등록, 수정, 삭제)

async function getUser() {
    try {
        const res = await axios.get('/users');
        const users = res.data;
        const list = document.getElementById('list');
        list.innerHTML = '';

        Object.keys(users).map(function (key) {
            const userDiv = document.createElement('div');
            const span = document.createElement('span');
            span.textContent = users[key];

            const modify = document.createElement('button');
            modify.textContent = 'modify';
            modify.addEventListener('click', async () => {
                const name = prompt('바꿀 이름을 입력하세요');
                if (!name) return alert('반드시 이름을 입력해야 합니다');
                try {
                    await axios.put('/user/' + key, { name });
                    getUser();
                } catch (error) {
                    console.log(error);
                }
            });

            const remove = document.createElement('button');
            remove.textContent = 'remove';
            remove.addEventListener('click', async () => {
                try {
                    await axios.delete('/user/' + key);
                    getUser();
                } catch (error) {
                    console.log(error);
                }
            });

            userDiv.appendChild(span);
            userDiv.appendChild(modify);
            userDiv.appendChild(remove);
            list.appendChild(userDiv);
            console.log(res.data);
        })
    } catch (error) {
        console.log(error);
    }
}

window.onload = getUser;
document.getElementById('form').addEventListener('submit', async (e) => {
    e.preventDefault();
    const name = e.target.username.value;
    if (!name) return alert('이름을 입력하세요');
    try {
        await axios.post('/user', { name });
        getUser();
    } catch (error) {
        console.log(error);
    }
    e.target.username.value = '';
})

 

getUser() 함수는 서버에서 사용자 목록을 가져와서 화면에 출력하는 역할을 한다. axios(비동기 서버 통신)을 이용하여 서버에서 사용자 목록을 가져온 후 각 사용자에 대한 수정 및 삭제 버튼을 만들고 해당 기능을 추가한다. 이를 HTML 문서에 추가한다.

window.onload = getUser는 페이지가 로드될 때 getUser 함수를 호출하는 것을 의미한다. 즉 페이지가 로드될 때 사용자 목록을 가져와서 표시하게 된다. 그리고 페이지에서 form 요소의 submit 이벤트를 수신하고 아래 작성된 함수가 실행된다. 사용자가 입력한 이름을 서버로 보내고 서버에서 반환된 정보로 사용자의 정보를 업데이트하는 기능을 구현한다.

 

아직 라우팅을 구현하지 않아 두 페이지는 연결되어 있지 않다. 이제 사용자 데이터를 처리하는 백엔드 API가 필요하다. axios를 설정하고 GET, POST, PUT, DELETE 요청을 처리하는 서버 코드를 구현해보겠다.

 

BACKEND 구현

server.js

const http = require('http');
const fs = require('fs').promises;
const path = require('path');

const users = {};

http.createServer(async (req, res) => {
    try {
        console.log(req.method, req.url);
        if (req.method === 'GET') {
            if (req.url === '/') {
                const data = await fs.readFile(path.join(__dirname, 'index.html'));
                return res.end(data);
            } else if (req.url === '/about') {
                const data = await fs.readFile(path.join(__dirname, 'about.html'));
                return res.end(data);
            } else if (req.url === '/users')
                return res.end(JSON.stringify(users));
            // 주소가 /도 /about도 아닌 경우
            try {
                const data = await fs.readFile(path.join(__dirname, req.url));
                return res.end(data);
            } catch (error) {
                // 404 NOT FOUND
            }
        }
        res.writeHead(404);
        return res.end('NOT FOUND');
    } catch (error) {
        console.log(error);
        res.end(error.message);
    }
}).listen(8080, () => {
    console.log('8080번 포트 서버 대기 중');
});

 

req.mothod로 HTTP 요청 메서드를 구분한다. /이면 메인페이지인 index.html를, /about이면 about.html 파일을 제공하고, 이외의 경우레는 주소에 적힌 파일을 제공한다. 만약 존재하지 않는 파일을 요청했거나 GET 메서드 요청이 아니라면 404 NOT FOUND 에러를 전송한다. 데이터베이스 대용으로 users 객체를 선언하여 사용자 정보를 저장한다.

페이지간 라우트 연결도 했기 때문에 server.js를 실행하고 http://localhost:8080/로 접속하면 버튼으로 페이지 간에 이동이 가능하다.

 

이제 나머지 POST, PUT, DELETE 메서드들도 구현하겠다.

	...
        } else if (req.method === 'POST') {
            if (req.url === '/user') {
                let body = '';
                req.on('data', (data) => {
                    body += data;
                });
                return req.on('end', () => {
                    console.log('POST 본문(body):', body);
                    const { name } = JSON.parse(body);
                    const id = Date.now();
                    users[id] = name;
                    res.end('Register Success');
                });
            }
        } else if (req.method === 'PUT') {
            if (req.url.startsWith('/user/')) {
                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') {
            if (req.url.startsWith('/user/')) {
                const key = req.url.split('/')[2];
                delete users[key];
                return res.end(JSON.stringify(users));
            }
        }
	...

 

서버를 실행시켜보자. Network 탭에서 네크워크 요청 내용을 실시간으로 볼 수 있다. Name은 요청 주소, Method는 요청 메서드, Status는 HTTP 응답 코드, Type은 요청 종류를 의미한다.

Home 버튼 클릭 시
About 버튼 클릭 시
사용자 등록
사용자 등록
사용자 수정
사용자 삭제

 

method탭은 name 우클릭해서 method에 체크하면 뜬다.

 

데이터가 메모리에 저장되믈 서버를 종료하면 데이터가 소실된다. 데이터를 영구적으로 저장하려면 데이터베이스를 사용해야 한다.

728x90