Nodejs 이용 기초적인 simple API Server 구현

류예린·2022년 7월 27일
0

1. Node.js based API Server


Client <-> API <-> Database 구조

현대 웹 시스템 아키텍처 구조는 기본적으로 Client, API, Database 이렇게 3티어 구조로 이루어져 있다(API는 API의 역할을 하는 물리적인 서버가 될 수도 있고, 서버 안에서 실행되고 있는 애플리케이션이 될 수도 있다. 보통은 물리적인 서버를 가리킨다) Client, API, Database가 각각 담당하고 있는 역할을 잘 수행할 때 웹 서비스가 온전하게 사용자들에게 서비스 될 수 있다.

[그림 1] 웹 3 티어 구조 (한 개의 API 서버와 Database로 서비스를 운영하는 방식)

위 이미지 구조에서 Client는 사용자가 웹 서비스를 이용하기 위해서 사용하는 웹 브라우저(Chrome, Safari), 모바일 앱, 데스크탑 프로그램, 그리고 다른 서버에 요청을 보내는 애플리케이션을 이야기한다.

Database는 웹 서비스에서 사용자가 읽고, 쓰고, 수정하고, 그리고 삭제하는 모든 데이터를 영구적으로 저장하고 있는 시스템이다.

API 서버는 Client와 Database 사이에서 Client의 복잡한 요청을 해석하여 데이터베이스에 데이터를 읽거나 쓰는 작업을 수행하고 그 결과를 다시 한번 가공해서 Client에게 전달해주는 역할을 수행한다.

API 만드는 방법

API 서버는 흔히 Database와 데이터 작업 수행을 위한 동적인 컨텐츠를 제공하기 위해 만들어진 WAS(Web Application Server)와 같은 개념이라고 볼 수 있다. 이렇게 Client의 요청을 동적으로 처리하는 API 서버를 만들기 위해서는 다음 3가지 도구가 필요하다.

  • 프로그래밍 언어 - Javascript, Python, Java, PHP 등
  • 프로그래밍 언어가 실행될 수 있는 환경인 런타임(Runtime) - Browser, Node.js, PVM, JVM 등
  • 운영체제가 설치되어 있는 물리적인 하드웨어(물리적인 서버) - MacOS 서버, Ubuntu 서버 등

물리적인 서버(Linux) 한 대에 Node.js 런타임을 설치하고, API 역할을 할 수 있는 javascript 코드를 Node.js 런타임에서 실행시키면 API 서버가 된다.

Browser 환경에서 javascript 코드를 실행시키면 화면을 랜더링하는, 필요한 데이터를 요청하는 클라이언트가 되는 것이다. Node.js로 크롤링하는 javascript 코드를 실행시키면 크롤링 스크립트가 되는 것이고, Node.js 런타임 환경에서 네트워크 요청을 처리할 수 있는 javascript 코드를 실행시키면 API 서버가 되는 것 입니다. API 서버는 Javascript와 Node.js 뿐만 아니라 Python과 PVM(Python Virtual Machine), Java와 JVM 등으로도 만들 수 있다.

Node.js 기반 API의 이점

API 서버는 반드시 Javascript 프로그래밍 언어와 Node.js 런타임 기반으로 만들지 않아도 방법은 다양하다. 자신이 가장 잘 이해하고 사용할 수 있는 언어를 선택해 구현하는 것이 가장 좋은 방법이다. 아래는 Node.js 기반의 API 서버가 갖는 장점이다.

✔️ Frontend / Backend 기술 스택 통합

  • 프론트엔드와 백엔드 양쪽에 동일한 언어를 사용하면 코드를 모듈로 만들어서 재사용할 수 있다.
  • Javascript로 훈련된 개발자는 최소한의 노력으로 서버 측 프로그래밍을 시작할 수 있다.
  • 현대 웹 개발에서 가장 중요한 개발팀의 전반적인 생산성 향상에 도움된다.

✔️ 빠른 처리 속도

  • C++로 작성된 V8엔진은 인터프리터 방식이 아닌 JavaScript로 작성된 코드를 컴퓨터가 해석하기 쉬운 바이트 코드로 변환하는 JIT 컴파일 방식을 채택하고 있으며, V8의 최적화 기법으로 놀라운 속도로 작업을 수행한다. Google이 엔진에 막대한 투자를 한 덕분에 V8은 매년 성능 향상을 보여주고 있다.
  • Node.js에는 V8 엔진과 더불어 libuv라는 비동기 I/O에 중점을 둔 오픈 소스 라이브러리를 사용함으로써 Event DrivenSingle Threadnon-blocking I/O 모델을 구현한다. 덕분에 Node.js는 지연(blocking) 없이 동시 요청을 빠르게 처리할 수 있습니다.

✔️ MSA (Micro Service Architecture)에 적합

  • MSA란 시스템의 전체적인 구조를 서비스별로 독립적으로 나뉘어서 구성되는 아키텍쳐 방식으로 기존에 Monolithic System이 갖는 한계를 해결하는 방법론이다. 쉽게 설명하면, 하나의 서버에 결제, 인증, 스트리밍 등의 모든 기능이 포함되어 있는 구조가 monolithic 방식이고, 결제 기능, 인증 기능을 분리해서 하나의 시스템에서 하나의 기능만 하도록 만들어진 구조가 MSA라고 이해할 수 있습니다.

[그림 2] Micro Service Architecture (MSA)

  • MSA는 요즘 널리 사용되는 아키텍처다. MSA 구조 안에서는 기능이 추가될 때마다 기존 시스템에 더해지는 것이 아니라 가벼운 시스템을 새롭게 구축하는 방법으로 확장한다. 결과적으로 Node.js는 상대적으로 가벼운 런타임이기 때문에 MSA와 아주 잘 조화되어 사용될 수 있다.

위에서 설명한 장점 이외에도 node.js는 풍부한 생태계(라이브러리 약 80만개)를 갖고 있고, JSON 형식을 그대로 사용할 수 있는 등 많은 장점들이 있다. 이러한 이유로 자바스크립트와 노드로 백엔드 API 서버를 구축하는 것은 좋은 선택일 수 있다. Node.js의 경우 속도와 확장성을 요구하는 집중적인 I/O가 있는 실시간 애플리케이션, 웹, 앱 어플리케이션 개발에 적합한 기술입니다. 최근 Node.js는 엔터프라이즈급(Netflix, NASA, 페이팔 등)에서 Node.js를 채택해서 활발히 사용되고 있다. 페이팔, 넷플릭스, 월마트, 링크드인, 우버 등에서 메인 또는 서브 서버로 사용하고 있다.





2. Let’s build API with http module


Node.js 기반의 API를 만드는 과정을 살펴본다. 이 과정에서 위스타그램(Westagram) 웹 시스템을 개발해서 서비스하는 가상의 스토리를 가정한다. [그림 3]은 최종적으로 만들고자는 Westagram Backend API 시스템 아키텍처로서, Client의 요청을 받는 Node.js based API와 MySQL based database로 구성되어 있다.

[그림 3] 최종 목표로하는 아키텍처와 필요한 개념들

먼저, [그림 3]에서 보는 것과 같이 데이터베이스와 관련된 내용을 제외하고 Client(크롬 브라우저)와 Node.js 기반 API 통신에 관련된 내용으로 축소해서 다루며 이후 데이터베이스를 연결하는 구조로 확장해 나간다.

[그림 4] Node.js based API

API 기능 정의

API 개발에 있어 가장 선두될 일은 API가 어떤 기능을 제공할지 정하는 것이다. 현재 위스타그램 백엔드 API 시스템을 개발해 서비스한다 가정하고 있다다. 그러므로 위스타그램에서 필요한 백엔드 API가 기능들을 정의해야 한다. 위스타그램에서 제공할 기능 목록은 다음과 같다.

  • 회원가입 생성 요청 처리 및 결과 반환
  • 게시물 생성 요청 처리 및 결과 반환
  • 게시물 목록 조회 요청 처리 및 결과 반환
  • 특정 게시물 조회 요청 처리 및 결과 반환
  • 특정 게시물에 댓글 생성 요청 처리 및 결과 반환
  • 특정 게시물에 댓글에 댓글 생성 요청 처리 및 결과 반환
  • 특정 게시물에 달린 댓글 목록 조회 요청 처리 및 결과 반환

API 코드 작성

✔️ HTTP server 객체 생성

클라이언트에게 Hello World!를 반환하는 간단한 코드를 작성해볼 수 있다. 가장 먼저, API 역할을 할 수 있는 코드를 작성하기 위해서 app.js라는 파일을 생성한다.

// app.js
const http = require('http'); // (1)

이후 위 주석(1) http 웹서버를 만들기 위해 Node.js 내에 내장되어 있는 http 모듈을 require()로 불러온다. 여기서, require()는 다른 프로그래밍 언어 또는 자바스크립트 ES6의 import와 유사한 기능이다. node.js는 require 후에 해당 모듈을 http라는 변수에 담아 하나의 독립적인 객체로 사용한다.

// app.js
const http = require('http');

// HTTP 서버 객체 생성
const server = http.createServer(); // (2)

주석(2) 코드와 같이 http 모듈에는 createServer([options], requestListener)라는 서버를 생성하는 기능(메서드)이 포함되어 있다. createServer([options], requestListener) 함수는 두가지 파라미터를 선택적으로 받을 수 있다. client와 connection을 얼마동안 유지할 것인지 결정하는 keepAliveTimeout과 같은 options와 http request가 들어왔을 때, 실행되어야하는 requestListener 함수가 있다.

두 가지 파라미터 모두 선택적으로 받을 수도 혹은 받지 않을 수도 있다. 또한 requestListener는 createServer() 메서드를 호출할 때 인자로 전달해줘도 되지만, 나중에 등록해줄 수도 있다. 여기서는 나중에 등록하는 방식으로 설명을 이어나간다.

이제 Client에서 HTTP 요청이 들어왔을 때, 실제로 실행되는 되는 함수(requestListner)를 정의해보자.

// app.js
const http = require('http');

// HTTP 서버 객체 생성
const server = http.createServer();

// HTTP 요청(이벤트)이 발생하면 실행되는 Listener(함수) 정의
const httpRequestListener = function (request, response) { // (3)
  response.writeHead(200, { "Content-Type": "application/json" });
  response.end(JSON.stringify({ message: "Hello World!" }));
};

주석(3)은 request와 response를 파라미터(parameters)로 갖으며 결과로 { message: "Hello World!" } 메시지를 반환하는 함수다. request와 response는 httpRequestListener 함수를 호출하는 호출부에서 인자(arguments)로 넣어주는 값이다. request와 response는 어떤 값이고, httpRequestListener 함수는 어디서 호출하는지는 아래에서 설명한다.

쉽게 말해, http 요청을 받아서 응답할 수 있는 함수를 만들기 위해서는 위와 같은 형태로 함수를 만들어야 한다. 그러나 지금까지의 코드론 http 요청을 받았을 때 httpRequestListener 함수를 실행할 수 없다. 서버가 http 요청를 이벤트(event)로 인식해 http 요청 이벤트 발생 때마다 httpRequestListener 함수를 실행할 수 있도록 이벤트로 등록하는 코드를 작성해보자.

(http 요청을 이벤트라고 언급한 이유는 Node.js가 이벤트 기반으로 동작하기 때문에, 정확한 설명을 위해서 언급 했다. 아직 Node.js의 이벤트 기반 동작원리를 얘기하기에는 내용이 어렵고 많기 때문에 서버가 클라이언트의 http 요청을 http request 이벤트로 인식한다는 것만 기억하면 좋다)

// app.js
const http = require('http'); // (1)

// HTTP 서버 객체 생성
const server = http.createServer(); // (2)

// HTTP 요청(이벤트)이 발생하면 실행되는 Listener(함수) 정의
const httpRequestListener = function (request, response) { // (3)
  response.writeHead(200, { "Content-Type": "application/json" }); // (4)
  response.end(JSON.stringify({ message: "Hello World!" }));  // (5)
};

// http request가 발생하면 httpRequestListener가 실행될 수 있도록
// request 이벤트에 httpRequestListener(함수)등록
server.on("request", httpRequestListener); // (6)

http.createServer() 메서드 실행 결과 반환된 서버 객체가 가지고 있는 on() 메서드를 사용해서 이벤트를 등록할 수 있다. on() 메서드를 호출할 때 인자로 request라는 문자열과 httpRequestListener 함수를 전달한다. 이렇게 server.on(“request”, httpRequestListener) 메서드를 호출하면 server 객체에 “request” 이름으로 이벤트가 등록된다.

이후에 서버로 실제로 http 요청이 들어오면, “request” 이름으로 등록된 httpRequestListener 함수에 인자로 클라이언트의 요청에 대한 정보가 담긴 request 객체와 응답에 대한 정보가 담긴 response 객체를 넘겨주면서 내부 로직이 실행된다. 그래서 httpRequestListener 함수의 block({ }) 내부에서는 request 와 response로 넘어오는 어떤 값을 사용할 수 있게 된다. 이렇게 전달받은 response 객체 내부의 여러가지 메서드(wrtieHead, end, 등)를 호출해서 클라이언트에게 응답을 보낼 수 있다.

✔️ response 객체

위 httpRequestListener 함수에서 파리미터로 받아서 사용하는 request와 response 객체에 대해서 살펴보자. response 객체는 서버로 웹브라우저나 또는 앱으로 부터 어떤 요청이 있을 때 요청한 사용자 측으로 값을 반환해 줄 때 사용하는 객체다. (4)주석 코드에서 response.writeHead()라는 메서드에 첫번째는 200 이라는 숫자값을, 두번째는 { } 중괄호 안에 { '키' : '값' } 형태의 값을 넣어서 호출하고 있다.

response.writeHead(200, {'Content-Type':'application/json'}); //(4)

첫번째 200 이라는 숫자값은 웹서버 들어오는 어떤 요청에 대해 정상적으로 값을 리턴할 때 사용하는 http status code다. 오류가 없이 서버에서 처리가 정상적으로 완료되면 200 코드를 담아서 응답헤더를 설정해 주게 된다.

두번째 {'Content-Type' : 'application/json'}값은 response message의 body에 담기는 컨텐츠의 타입이 application/json 형식이라는 것을 정의한다. 브라우저에서는 Content-Type 헤더를 기준, 값을 json 형태로 화면에 출력해준다. 이렇게 { } 블럭형태로 값이 전달되는 경우는 해당 블럭에 복수개의 값이 담길 수 있다는 의미다. 두번째 값은 Content-Type 키값 이외에도 Authorization, Cookie 등 다양한 값들을 지정할 수 있다.

주석(5)에서 response.end( )라는 함수에 { message: "Hello World!" } 객체를 response message의 body에 담아서 클라이언트에 반환한다. 이렇게 실제 코드 값을 end( ) 함수의 인자로 전달해서 호출하면 브라우저는 해당 컨텐츠를 받은 후 json 형태로 화면에 출력해 준다.

response.end(JSON.stringify({ message: "Hello World!" })); //(5)

✔️ request 객체

request 객체는 요청을 보낸 클라이언트와 관련된 데이터가 담겨있는 객체다. 클라이언트의 IP, 요청에서 사용한 URL, HTTP Method(GET, POST, DELETE, PATCH) 등 다양한 정보들이 저장되어 있다. 아래와 같이 request 객체를 console.log()로 출력해보면, 클라이언트의 정보를 받을 수 있다.

// app.js
const httpRequestListener = function (request, response) {
  console.log(`request object : ${request}`)
  response.writeHead(200, { "Content-Type": "application/json" });
  response.end(JSON.stringify({ message: "Hello World!" }));
};

request 객체를 출력해보면, 정말 많은 값들이 포함되어 있습니다. 모든 것을 공부하기 보다는 API 구현에 필요한 request.urlrequest.method 속성에 대해서만 알고 있어도 충분하다.

HTTP server 실행

컴퓨터와 컴퓨터가 통신하기 위해서는 IP 주소와 포트 번호를 사용한다. 쉽게 설명하면, IP 주소는 집주소와 같다. 인터넷에 연결된 수많은 컴퓨터마다 부여받는 주소로 고유한 값을 갖는다. IP 주소 덕분에 많은 컴퓨터 중에서 내가 원하는 컴퓨터에 정확하게 요청을 보낼 수 있다.

IP 주소만으로 A 컴퓨터에서 B 컴퓨터에 요청을 보내는 것은 택배원에게 물건을 보내면서 아파트 이름만 적어준 것과 같다. 서버에는 여러가지 애플리케이션(크롬, 카카오톡, http 서버(API))이 각 고유한 포트 번호를 가지고 실행되고 있다.

만약에 클라이언트에서 요청을 보낼 때, 127.0.0.1 IP 주소의 컴퓨터에서 실행되고 있는 http 서버에 요청을 보내고 싶다면 IP 주소에 포트 번호를 더해서 정확하게 요청을 전달할 수 있다.

이제 마지막으로 server 객체에서 제공해주는 server.listen(포트 번호, IP 주소, 콜백함수) 메서드를 사용해서 Node.js에서 클라이언트의 요청 수신을 대기도록 서버를 실행시켜줘야 한다.

// app.js
const http = require('http'); // (1)

// HTTP 서버 객체 생성
const server = http.createServer(); // (2)

// HTTP 요청(이벤트)이 발생하면 실행되는 listener(함수) 정의
const httpRequestListener = function (request, response) { // (3)
  response.writeHead(200, { "Content-Type": "application/json" }); // (4)
  response.end(JSON.stringify({ message: "Hello World!" })); // (5)
};

// http request가 발생하면 httpRequestListener가 실행될 수 있도록
// request 이벤트에 httpRequestListener(함수) 등록
server.on("request", httpRequestListener); // (6)

server.listen(8000, '127.0.0.1', function() { // (7)
    console.log('Listening to requests on port 8000');
});

주석(7)은 생성된 서버가 8000 포트를 수신 포트로 바라보고(listening port) http 수신을 대기한다. 즉, listen() 함수를 호출해야만 server가 해당 포트를 리스닝하면서 수신을 대기한다. 여기서, listen() method는 시간이 걸릴 수 있는 작업이라서 callback으로 비동기적으로 동작한다. 즉, listen이 완료되었을 때, callback이 실행되면서 'Listening to requests on port 8000' 가 출력된다.

마지막으로 터미널에서 node.js로 app.js 파일을 실행한다. 아래와 같이 'Listening to requests on port 8000' 메시지가 출력되면 정상적으로 서버가 실행된 것을 의미한다.

$ node app.js
Listening to requests on port 8000

서버에 요청 보내기

✔️ Client 툴 설치

먼저 브라우저와 같이 http 요청을 보낼 수 있는 개발용 클라이언트 도구인 httpie를 설치한다.

#Ubuntu
$ sudo apt install httpie

#Mac
$ brew install httpie

✔️ HTTP 요청 보내기

서버가 정상적으로 동작하는지 확인하기 위해 httpie라는 Client 도구를 사용해서 HTTP 요청을 보내본다. 아래 httpie client 명령어를 참고.

$ http -v GET 127.0.0.1:8000

// request message
GET / HTTP/1.1          // start line. http method와 http protocol 버전을 명시.
Accept: */*                    // header start. 아래 네번째 줄까지. request에 관한 전반적인 메타 데이터.
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: 127.0.0.1:8000
User-Agent: HTTPie/2.6.0

// response message
HTTP/1.1 200 OK       // http protocol version, status code, status message.
Connection: keep-alive    // header start. 아래 네번째 줄까지.
Content-Type: application/json // res.writeHead 메서드에 두번째로 넘겨줬던 인자값. {"Content-Type": "applicaiton/json"}
Date: Mon, 07 Feb 2022 14:45:22 GMT
Keep-Alive: timeout=5
Transfer-Encoding: chunked

{
    "message": "Hello World!"   // body. message가 담긴 Object.
}

“Hello World!”라는 결과를 출력해주는 것으로 API 서버가 정상 동작하고 있음을 확인할 수 있다. 여기까지는 아파트로 비유하면, 아파트는 만들었는데 아직 몇 동 몇 호는 만들지 않은 상태라고 할 수 있다. 마치 택배원(Client)이 배송 주소를 보고 물건(request)을 가지고 왔는데 아파트까지만 도달한 경우다. 그렇기 때문에 물건이 제대로 목적지에 도달하기 위해서는 101동 1001호까지 만들어줘야 한다. 우리는 이것을 엔드포인트라 부른다.





3. Instagram Endpoint 구현


ping 엔드포인트 구현하기

첫 API 개발은 ping 엔드포인트(endpoint)를 구현하는 것부터 시작한다. 엔드포인트API 서버가 제공하는 통신 채널 혹은 접접이다. 클라이언트가 백엔드 API 서버와 통신할 때 엔드포인트에 접속하는 형태로 통신하게 된다. 각 엔드포인트는 고유의 URL 주소를 가지게 되며, 아래와 같이 고유의 URL 주소를 통해서 해당 엔드포인트에 요청을 보낼 수 있다. 일반적으로 각 엔드포인트는 고유의 기능을 담당하고 있으며, 이러한 엔드포인트들이 모여서 하나의 API를 구성한다.

크롬 브라우저(Client)와 Httpie(개발용 Client)를 사용해서 ping 엔드포인트에 요청을 보내고 응답을 받을 수 있습니다.

[그림 3-1] 브라우저에서 127.0.0.1:8000/ping URI로 요청을 보내고 있는 상황

개발용 클라이언트 도구인 Httpie를 사용해서 엔드포인트를 호출하게 되면, 상세하게 요청 메세지와 응답 메세지 구조를 볼 수 있다. 중요하게 봐야할 부분은 요청 메세지의 최상단에 있는 start line이다.
GET /ping HTTP/1.1 에서 GET은 http method으로 해당 엔드포인트에 어떤 엑션을 요구하는지를 의미합니다. http method에서 GET은 무언가를 받아오는 의미를 가진 액션, POST는 무언가를 보내는 액션, DELETE는 삭제하는 액션을 담고 있습니다. 그 다음으로 나오는 /pingtarget이라고 부르고, /ping target과 연결되어 요청을 실질적으로 처리하는 부분을 엔드포인트라고 볼 수 있다.

$ http -v GET 127.0.0.1:8000/ping

**# Request message 구조**
GET /ping HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: 127.0.0.1:8000
User-Agent: HTTPie/3.0.2

**# Response message 구조**
HTTP/1.1 200 OK
Connection: keep-alive
Content-Type: application/json
Date: Tue, 26 Apr 2022 01:49:20 GMT
Keep-Alive: timeout=5
Transfer-Encoding: chunked

{
    "message": "pong"
}

이제, 위에서 미리 보여주었던 API 서버의 상태를 확인(health check) ping 엔드포인트를 API에 추가해 보자. ping 엔드포인트는 http:127.0.0.1/ping의 URL로 요청을 보냈을 때, 서버가 정상적으로 실행되고 있다면, “pong”이라는 텍스트를 반환(return)하는 엔드포인트다. ping 엔드포인트는 API 서버에 접속하지 않고 해당 API가 정상적으로 운행하고 있는지 여부를 확인하는 용도로 많이 사용된다.

// app.js

const http = require('http');
const server = http.createServer();

const httpRequestListener = function (request, response) {
  const { url, method } = request  // (1)
	if (method === 'GET') { // (2)
		if (url === '/ping') { // (3)
			response.writeHead(200, {'Content-Type' : 'application/json'}); // (4)
			response.end(JSON.stringify({message : 'pong'}) // (5)
		}
	}
};

server.on("request", httpRequestListener);

server.listen(8000, '127.0.0.1', function() {
    console.log('Listening to requests on port 8000');
});

실행중인 API server가 http request를 받게 되면, server 객체에 등록한 “request” 이벤트에 연결되어 있는 httpRequestListener가 실행된다. 이때, callback 함수의 인자로 request와 response 객체를 내부적으로 만들어서 전달해 준다.

request 객체에는 http request message에 담겨있는 대부분의 정보가 담겨지게 된다.

(1)은 request 객체에서 url과 http_method 정보를 추출해서 url, method 변수에 할당한다.

(2), (3) 코드에서 지금 request가 어떤 http 메소드로 요청했고, target이 어디인지를 판별한다.

(3) target이 /ping이라면

(4), (5) 코드가 실행된다. 이때 두 번째 인자로 받은 response 객체를 사용하게 되는데, response 객체를 사용해서 클라이언트로 부터 받은 요청에 대해서 어떤 응답을 보낼지 결정한다.

(4)에서는 response message의 body에 어떤 데이터 타입을 보낼지를 결정하는 header를 설정한다.

(5) response.end() 함수를 호출하게되면 다시 클라이언트에게 http 응답을 보내게 된다. 여기서 response 객체의 end() 메소드에 인자로 넘겨준 값이 응답 메세지 구조 중 body에 담겨서 보내지게 되는 것이다. JSON.stringify({message : 'pong'})는 Javascript Object를 JSON화 해서 body에 넣어주기 위해서 사용하는 모듈이다.

지금까지 학습한 내용을 보면, 사실 엔드포인트를 구현한다는 것은 결국 일반적인 함수를 구현하는 것과 큰 차이가 없다. 이러한 관점에서 보면 백엔드 API 개발도 구조적으로는 크게 어렵거나 복잡할 것이 없다. 해당 API가 제공하는 기능들, 즉 비지니스 로직(business logic)을 구현하는 함수들을 개발하는 것이 백엔드 API에서 가장 많은 부분을 차지한다.

회원가입 엔드포인트 구현하기

위스타그램의 핵심 기능중에 하나인 회원 가입 기능을 구현하면 API 개발에 조금 더 친숙해질 수 있다. 실제 인스타그램은 굉장히 많은 사용자가 이용하기 때문에 동시 접속 사용자 수가 많다. 많은 동시 접속들을 빠르게 처리하는 것은 매우 어려운 일이고, 그러한 시스템을 구현하기 위해서는 시스템 측면 그리고 로직 구현 측면에서 많은 것들을 고려해야 한다.

이번에 구현하는 회원가입 기능 개발은 백엔드 개발 입문을 위한 목적이다. 그러므로 수 많은 동시 접속이나 HTTP 요청 처리 속도를 고려하지 않고 구현해보도록 한다.

✔️ 회원가입 절차

회원가입 절차는 간단하다. 사용자로부터 이름, 이메일, 비밀번호 등의 기본적인 회원 정보를 HTTP 요청을 통해 받은 후 시스템에 저장하면 된다. 회원가입 필요한 정보는 다음과 같다고 가정한다.

  • id
  • name
  • email
  • password

✔️ 회원가입 요청 메세지 만들기

$ http -v POST 127.0.0.1:8000/user \
	id:=1 \
	name="홍길동" \
	email="honghong@gmail.com" \
	password="1q2w3e4r"

# Start Line
**POST /user HTTP/1.1**

# Header
****Accept: application/json, */*;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 94
Content-Type: application/json
Host: 127.0.0.1:8000
User-Agent: HTTPie/3.0.2

# Body
{
    "email": "honghong@gmail.com",
    "id": 3,
    "name": "홍길동",
    "password": "1q2w3e4r"
}

✔️ 응답 메세지 만들기

# Status Line
HTTP/1.1 200 OK

# Header
Connection: keep-alive
Content-Length: 2
Date: Tue, 26 Apr 2022 02:32:47 GMT
Content-Type: application/json
Keep-Alive: timeout=5

# Body
{
    "message": "ok!",
}

✔️ 엔드포인트 구현

다음은 회원가입 기능을 구현하는 엔드포인트 코드다. 위에서 작성한 ping 엔드포인트가 추가된 코드에 회원가입 엔드포인트 코드를 더하는 방식으로 구현한다.

// app.js
const http = require('http'); // (1)
const server = http.createServer();

const users = [ // (2)
  {
    id: 1,
    name: "Rebekah Johnson",
    email: "Glover12345@gmail.com",
    password: "123qwe",
  },
  {
    id: 2,
    name: "Fabian Predovic",
    email: "Connell29@gmail.com",
    password: "password",
  },
]

const posts = [
  {
    id: 1,
    title: "간단한 HTTP API 개발 시작!",
    description: "Node.js에 내장되어 있는 http 모듈을 사용해서 HTTP server를 구현.",
    userId: 1,
  },
  {
    id: 2,
    title: "HTTP의 특성",
    description: "Request/Response와 Stateless!!",
    userId: 1,
  },
];

const httpRequestListener = function (request, response) {
  const { url, method } = request
	if (method === 'GET') {
		if (url === '/ping') {
			response.writeHead(200, {'Content-Type' : 'application/json'});
			response.end(JSON.stringify({message : 'pong'}));
		}
	} else if (method === 'POST') { // (3)
		if (url === '/users') {
			let body = ''; // (4)
			request.on('data', (data) => {body += data;}) // (5)

			// stream을 전부 받아온 이후에 실행
			request.on('end', () => {  // (6)
				const user = JSON.parse(body); //(7)

				users.push({ // (8)
					id : user.id,
					name : user.name,
					email: user.email,
					password : user.password
				})

				response.end(JSON.stringify({message : 'ok!'}); // (9)
			})
    }
  }
};

server.on("request", httpRequestListener);

server.listen(8000, '127.0.0.1', function() {
    console.log('Listening to requests on port 8000');
});
  • (2) 새롭게 회원가입 하는 사용자 정보를 저장할 배열을 users라는 변수에 정의한다. 배열 안에 객체 형태로 회원 정보가 저장될 것이다.
  • (3) “/ping” 엔드포인트와 마찬가지로 request message에서 꺼내온 http method(POST), tartget(/users) 정보와 if문을 사용해서 엔드포인트를 정의한다.
  • (4), (5) HTTP 요청을 통해 전송된 body에 담긴 회원 정보를 읽어 들인다. 간단하게는 짧은 단위로 나누어져서 받아지는 body에 담겨있는 데이터를 하나로 합쳐서 body라는 변수에 정의한다.
  • (6) 4, 5 과정에서 데이터를 정상적으로 받아온 이후에 자동으로 실행되는 코드다. 정확히는 request.on() 함수에 인자로 전달한 arrow function이 실행된다.
  • (7) JSON.parse()를 활용해서 HTTP 요청을 통해 전송된 JSON 데이터를 javascript object로 변환해 준다.
  • (8) Client로부터 받은 사용자 정보를 객체 형태로 만들어서 users 배열에 추가해주면서, 회원 등록을 완료한다.
  • (9) 마지막으로 회원가입이 성공적으로 끝났음을 알리는 “ok!” 메세지를 응답으로 보내준다.

ping 엔드포인트와 회원가입 엔드포인트를 구현해 봤다. 회원가입 엔드포인트의 경우 아직 완벽하게 구현된 코드라고 볼 수는 없다. 현재 회원 정보를 users라는 배열에 저장하고 있기 때문이다. 이는 API가 실행되고 있는 서버의 메모리에 저장되어 있음을 의미하며, API 서버를 종료하고 다시 사직한다면 users 배열에 저장되어 있는 모든 정보는 사라지게 된다. 굉장히 크리티컬한 문제다.





4. 정리


  • 현대 웹 시스템 아키텍처 구조는 기본적으로 Client, API, Database의 3티어 구조로 이루어져 있다.
  • API는 API의 역할을 하는 물리적인 서버가 될 수도 있고, 서버 안에서 실행되고 있는 애플리케이션이 될 수도 있다. 보통은 물리적인 서버를 가리킨다.
  • API를 만들기 위해서는 프로그래밍 언어, 프로그래밍 언어가 실행될 수 있는 환경인 런타임(Runtime), 그리고 운영체제가 설치되어 있는 물리적인 하드웨어(물리적인 서버)가 필요하다.
  • 컴퓨터와 컴퓨터가 통신하기 위해서는 IP 주소와 포트 번호를 사용한다.
  • Node.js에 내장되어 있는 http 모듈을 사용해서 API(http 서버)를 만들 수 있습니다.
    • server = http.createServer() 메서드로 HTTP 서버 객체를 생성하고, HTTP 요청(”request” 이벤트)이 발생하면 실행시킬 httpRequestListener 함수를 구현하고, server.on() 메서드를 사용해서 “request” 이벤트와 httpRequestListener 함수를 이벤트로 등록한다.
    • server.listen() 메서드를 사용해 server가 IP:PORT 로 들어오는 http 요청을 수신을 대기도록 서버를 실행한다.
  • 만든 API에 서버의 상태를 확인하는 ping 엔드포인트와 회원가입 엔드포인트를 추가해서 발전시킨다.
profile
helloworld

0개의 댓글