Node.js를 활용한 간단한 REST API 구현하기: 실전 가이드

1. Node.js와 REST API 개요

Node.js는 Chrome V8 JavaScript 엔진으로 빌드된 JavaScript 런타임입니다. 이를 통해 개발자들은 클라이언트 사이드 코드 외에도 서버 사이드 코드를 JavaScript로 작성할 수 있습니다. 대부분의 작업을 비동기적으로 수행하여 높은 성능을 보장합니다.


// 간단한 http 서버 구축 예제
const http = require('http');

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello World');
});

server.listen(3000, '127.0.0.1', () => {
  console.log(`Server running at http://127.0.0.1:3000/`);
});

추가로, REST(Representational State Transfer) API는 웹 기반 서비스에서 자주 사용되는 API 디자인 아키텍처 중 하나입니다. REST 원칙을 따르며, URL을 통해 리소스를 표시하고 HTTP 동사(GET, POST, PUT, DELETE)를 통해 해당 리소스에 대한 동작(CRUD 작동)을 실행하는 방식입니다.


GET /users             # 사용자 리스트 불러오기
POST /users            # 사용자 생성하기
GET /users/:userId     # 특정 사용자 정보 불러오기
PUT /users/:userId     # 특정 사용자 정보 업데이트하기
DELETE /users/:userId  # 특정 사용자 삭제하기

2. Node.js 설치 및 환경 설정

2.1 Node.js 설치

Node.js는 공식 웹사이트(https://nodejs.org/)에서 다운로드 받을 수 있습니다. LTS(Long Term Support) 버전과 Current 버전 중에서 선택할 수 있지만, 안정성을 위해 대부분의 경우 LTS 버전을 설치하는 것이 좋습니다.

2.2 환경 설정

Node.js 설치가 완료되면, 명령 프롬프트나 터미널을 열고 아래의 명령을 통해 설치가 잘 진행되었는지 확인할 수 있습니다.


//Node.js 버전 확인
node -v

//npm 버전 확인
npm -v

Node.js 설치 후에는 개발 환경에 따라 추가적으로 여러 패키지들을 설치하는 것이 일반적입니다. 예를 들면, nodemon은 소스 코드 변경 시 자동으로 서버를 다시 시작해주는 역할을 하는 도구입니다.


//nodemon 설치
npm install -g nodemon

3. REST API 이해

3.1 REST 원칙

REST(Representational State Transfer)는 웹 서비스 아키텍처를 위한 디자인 패턴으로, 다음과 같은 원칙을 따릅니다:

1. Stateless: 모든 요청은 각각 독립적으로 처리되어야 합니다. 이전 요청이 현재 요청에 영향을 미치지 않아야 합니다.

2. Client-Server: 클라이언트와 서버는 서로 독립적으로 동작해야 하며, 각 요청과 응답은 독립적으로 처리되어야 합니다.

3. Cacheable: 클라이언트는 응답을 캐싱할 수 있어야 합니다. 이를 통해 성능이 향상됩니다.

4. Layered System: 클라이언트는 서버와 직접 통신하든, 중간 서버를 통하든지 상관없이 동일하게 동작해야 합니다. 이는 확장성을 높이는 데 도움이 됩니다.

3.2 HTTP 메소드

HTTP 메소드는 요청의 종류와 행동을 결정합니다. 대표적인 HTTP 메소드로는 GET, POST, PUT, DELETE가 있습니다.


GET /users             # 사용자 리스트 불러오기
POST /users            # 사용자 생성하기
GET /users/:userId     # 특정 사용자 정보 불러오기
PUT /users/:userId     # 특정 사용자 정보 업데이트하기
DELETE /users/:userId  # 특정 사용자 삭제하기

이와 같이 HTTP 메소드를 이용하면 클라이언트는 서버에게 어떤 리소스에 대해 어떤 동작을 수행하고 싶은지를 명확하게 요청할 수 있습니다.


4. Node.js를 이용한 서버 구축

4.1 Express.js 설치

Express.js는 Node.js를 위한 가장 대중적인 웹 프레임워크입니다. 이를 이용하면 손쉽게 서버를 구성할 수 있습니다. npm을 이용해 Express.js를 설치합시다.


// Express.js 설치
npm install express

4.2 서버 생성

Express.js를 이용해 다음과 같이 간단하게 서버를 생성할 수 있습니다.


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

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

app.listen(port, () => {
  console.log(`Server is running at http://localhost:${port}`);
});

이 코드는 3000포트에서 서버를 실행하고, 루트 URL(‘/’)으로 GET 요청이 들어올 경우 ‘Hello, World!’ 문자열을 응답하는 간단한 웹 서버를 생성합니다.


5. Route 설정

5.1 Route 이해

루트는 서버가 클라이언트 요청에 대해 어떻게 응답할지를 결정하는 역할을 합니다. URL 경로와 HTTP 메소드에 따라 특정 함수(핸들러)를 실행합니다.

5.2 Route 설정

Express.js를 이용하면 다음과 같이 다양한 루트를 설정할 수 있습니다.


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

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

app.get('/users', (req, res) => {
    // 사용자 리스트를 불러오는 코드
});

app.post('/users', (req,res) => {
    // 사용자를 생성하는 코드
});

app.get('/users/:userId', (req,res) => {
    // 특정 사용자의 정보를 불러오는 코드
});

app.listen(3000, () => {
    console.log('Server is running on port 3000');
});

GET, POST 메소드와 URL 경로를 이용해서 클라이언트의 다양한 요청에 맞게 응답할 수 있게 루트를 설정합니다.


6. REST API를 위한 라우터 작성

6.1 GET 라우터

GET 라우터는 웹 서버에서 데이터를 조회하는 데 사용됩니다. 다음은 사용자의 리스트를 불러오는 예시이며, ‘/users’ 경로를 통해 HTTP GET 요청을 받으면 모든 사용자 정보를 응답받습니다.


app.get('/users', (req, res) => {
    // 임시적으로 모든 사용자 데이터를 배열로 만들어서 반환하였습니다.
    const users = [
        {id: 1, name: 'Alice'},
        {id: 2, name: 'Bob'}
    ];
    res.json(users);
});

6.2 POST 라우터

POST 라우터는 웹 서버에서 새로운 데이터를 생성하는 데 사용됩니다. 다음의 기본적인 POST 라우터는 ‘/users’ 경로에서 HTTP POST 요청을 받아 새로운 사용자를 생성합니다.


app.post('/users', (req, res) => {
  // 클라이언트에서 전달된 정보를 이용해 새로운 사용자를 생성하는 코드가 위치합니다.
  res.json({message: "User has been created successfully."});
});

위 코드는 실제로 사용자를 생성하지는 않고, 클라이언트에게 ‘User has been created successfully.’ 메시지를 JSON 형태로 전송합니다. 실제 구현 시에는 데이터베이스에서 사용자를 생성하는 로직이 추가되어야 합니다.


7. 데이터베이스 연동

7.1 MongoDB 설치 및 연동

MongoDB는 NoSQL 데이터베이스 중 하나로, Node.js와 잘 작동합니다. 패키지 관리자 npm을 사용하면 손쉽게 설치할 수 있습니다.


npm install mongodb

설치가 완료되면 다음과 같이 연결을 시도할 수 있습니다.


const MongoClient = require('mongodb').MongoClient;

const url = "mongodb://localhost:27017/";

MongoClient.connect(url, function(err, db) {
  if (err) throw err;
  console.log("Database connected!");
  db.close();
});

7.2 데이터베이스 기본 작업

데이터베이스에 저장된 데이터를 읽기, 쓰기, 수정하기, 삭제하기와 같은 기본 작업을 수행할 수 있습니다.

다음은 “users” 컬렉션에 사용자를 추가하는 예화입니다.


MongoClient.connect(url, function(err, db) {
  if (err) throw err;
  var dbo = db.db("mydb");
  var myobj = { name: "John", age: "22" };
  dbo.collection("users").insertOne(myobj, function(err, res) {
    if (err) throw err;
    console.log("User inserted");
    db.close();
  });
});

위 코드를 수행하면, “mydb” 데이터베이스의 “users” 컬렉션에 이름이 “John”,나이가 “22”인 사용자를 추가합니다.


8. 동적 라우팅과 CRUD

8.1 동적 라우팅 작성

동적 라우팅은 URL의 일부를 변수처럼 처리하는 방식입니다. ‘:’ 문자 다음에 변수 이름을 지정하여 사용할 수 있습니다. 다음은 id 값을 동적으로 받아 해당 사용자의 정보를 조회하는 예시입니다.


app.get('/users/:id', (req, res) => {
    // id 값을 동적으로 받아 해당 user를 반환합니다.
    const id = req.params.id;
    res.send(id);
});

8.2 CRUD 작성 및 테스트

다음은 각 HTTP 메소드를 이용해 CRUD 작업을 하는 예시 코드입니다.


// Create
app.post('/users', (req, res) => {
  // user를 생성하는 코드
});

// Read
app.get('/users/:id', (req, res) => {
  // user를 조회하는 코드
});

// Update
app.put('/users/:id', (req, res) => {
  // user를 업데이트하는 코드
});

// Delete
app.delete('/users/:id', (req, res) => {
  // user를 삭제하는 코드
});

CRUD 작업을 테스트하기 위해서는 Postman 등의 HTTP 클라이언트 툴을 이용할 수 있습니다. 해당 툴을 이용해 각 HTTP 메소드와 경로, 필요한 데이터를 전송하여 결과를 확인할 수 있습니다.


9. 에러 핸들링

9.1 에러 핸들링 기법

애플리케이션 실행 중에 다양한 이유로 에러가 발생할 수 있습니다. 이러한 에러를 잘 처리해야, 사용자에게 정확한 정보를 전달하고 프로그램이 적절한 상태로 유지될 수 있습니다.


app.get('/users/:id', (req, res, next) => {
  // id 값을 동적으로 받아 해당 user를 조회하는 비동기 함수
  getUser(req.params.id).then((user) => {
    if (!user) {
      // user가 존재하지 않을 때 에러를 발생시킵니다.
      return next(new Error('User not found'));
    }
    res.json(user);
  }).catch((err) => {
    // 조회 도중 에러가 발생한 경우 처리를 담당한다.
    next(err);
  });
});

9.2 에러 핸들링 코드 작성

에러는 middleware를 이용하여 전역적으로 처리할 수 있습니다. 다음은 에러를 처리하는 전역 에러 핸들러 middleware의 예시입니다.


app.use((err, req, res, next) => {
  console.error(err.stack); // 에러 메시지를 로깅합니다.
  res.status(500).send('Something broke!'); // 에러 메시지를 응답합니다.
});

위 코드는 모든 요청 라우터 아래에 위치해야 합니다. err 객체를 첫 번째 매개변수로 받아 에러를 처리합니다.


10. API 테스트

개발한 API의 기능들이 올바르게 작동하는지 검증하기 위해서는 충분한 테스트가 필요합니다. 이를 위해 자동화된 테스트 코드를 작성하거나 Postman과 같은 API 테스트 툴을 사용하는 등 다양한 방법이 있습니다.

10.1 테스트 코드 작성

테스트 프레임워크 중 하나인 Mocha와 assertion 라이브러리인 Chai를 이용하여 테스트 코드를 작성해보겠습니다. Mocha와 Chai를 통해 HTTP 요청을 테스트하는 모듈인 chai-http도 함께 사용합니다.


const expect = require('chai').expect;
const chai = require('chai');
const chaiHttp = require('chai-http');
const app = require('../app');

chai.use(chaiHttp);

describe('GET /users', () => {
  it('should return all users', (done) => {
    chai.request(app)
      .get('/users')
      .end((err, res) => {
        expect(res).to.have.status(200);
        expect(res.body).to.be.a('array');
        done();
      });
  });
});

위의 테스트 코드는 ‘/users’ 라우트에 GET 요청을 보내는 경우를 테스트하며, 응답 상태 코드가 200이고, 응답 바디가 배열 형식인지 확인합니다.

10.2 Postman을 이용한 테스트

Postman은 API 개발을 위한 툴로, 요청을 보내고 응답을 확인할 수 있습니다. URI, 메소드, 헤더, 바디 등 요청에 필요한 모든 정보를 설정하고 요청을 보낼 수 있으므로 API 테스트에 효과적입니다.


1. Postman을 실행하고, 새로운 요청을 생성합니다.
2. HTTP 메소드와 요청을 보낼 URL을 입력합니다.
3. 필요하다면 헤더나 바디를 설정합니다.
4. 'Send' 버튼을 클릭하여 요청을 보냅니다.
5. 응답 상태 코드와 응답 바디를 확인합니다.

이렇게, API 테스트를 통해 개발한 API가 정상적으로 작동하는지 검증할 수 있습니다.


Leave a Comment