Node js에 관한 것 아무거나 2편, Node js 소개 (번역)

Jake Seo·2019년 4월 11일
7

anything-about-nodejs

목록 보기
2/4

들어가기 전에

시작

자바스크립트 환경에서 웹앱을 작성하는 방법에 대해 상세하게 소개합니다.

만일 당신이 프론트엔드 코드를 작성한다면, JS에 매우 의존도가 높기 때문에 노드 JS로 웹앱을 작성하는 것은 매우 쉬운 일일 것입니다.

노드 JS는 만능이 아니라는 것을 이해하는 것은 매우 중요합니다. 모든 프로젝트에서 노드 JS만을 선택하는 것은 능사가 아님을 알아두세요. 누구나 노드 JS로 서버를 만들 수 있지만, 큰 스케일의 웹앱을 작성하기 위해서는 언어에 대한 깊은 수준의 이해가 필요하다는 것을 명심하세요.

그럼 시작합니다.

Node.js. 이전

웹 어플리케이션은 클라이언트/서버 모델로 작성됐습니다. 클라이언트는 서버로부터 리소스를 요청하고 서버는 리소스를 응답했습니다. 서버는 오직 클라이언트가 요청했을 때만 응답을 했고 각각의 응답이 끝나면 연결을 닫아버렸죠.

이 패턴은 매우 효율적입니다. 왜냐하면 서버로 들어오는 모든 요청은 시간과 리소스(memory, CPU etc)가 들기 때문이죠. 요청된 리소스들에 대한 응답만 한 후에 서버를 닫아버리고 그래서 서버가 다른 요청들에도 응답할 수 있게 만드는 것은 똑똑한 선택이었습니다.

"어떻게 서버는 동시에 수백만의 요청에 이렇게 응답할 수 있었을까요?" 만일 당신이 이렇게 물어본다면, 당신은 이것이 그다지 좋은 방법이 아님을 알고 있는 것입니다. 만일 다른 요청이 전부 응답되기까지 클라이언트가 보내는 서버로의 요청이 딜레이 된다면 말이죠.

당신이 페이스북에 방문했는데 먼저 요청된 수천명의 사람들의 요청 때문에 5분을 기다리란 소리를 들었다는 것을 상상해보세요. 적어도 몇천 최소한 몇백개의 요청이 한번에 들어올 때 처리할 방법이 적어도 하나는 있어야 합니다. 사실 좋은 방법이 있는데, 그것은 스레드(Threads)라고 불리는 것입니다.

스레드는 시스템이 다중적인 명령을 동시에 수행하는 것을 가능하게 해줍니다. 모든 요청은 새로운 스레드를 열게 됩니다. 모든 스레드는 코드실행을 완수하기 위해 필요한 모든 것을 가지고 있습니다.

뭔가 이상하게 들린다구요? 이러한 비유를 사용해봅시다.

한 사람만 음식을 서빙하는 레스토랑을 상상해보세요. 음식을 주문하는 사람이 엄청나게 많다면 그 레스토랑은 정신이 하나도 없이 미쳐돌아갈 것입니다. 사람들은 대기중인 주문에 대한 서빙이 모두 끝날 때까지 기다려야 할 것입니다. 이 문제를 해결하는 유일한 방법은 음식을 서빙할 종업원을 더 구하는 것이겠죠? 이러한 방법으로, 한번에 더 많은 손님들이 음식을 받을 수 있을 것입니다.

개개의 스레드는 새로운 직원입니다. 그리고 브라우저는 배고픈 사람들입니다. 이젠 이해가 되겠죠?

하지만 이러한 체계는 안 좋은 점이 있습니다. 결국 어느 순간에는 엄청나게 많은 요청들이 들어오고 새로운 스레드들이 시작될 것이고 이러한 작업은 시스템 내의 모든 메모리와 리소스를 소비할 것입니다. 아까 식당 얘기로 돌아가보면, 더 많은 직원을 고용하려면 더 많은 돈과 공간이 필요하죠.

하지만 서버가 즉시 브라우저에 응답할 수 있는 것은 여전히 엄청난 장점 중 하나입니다. 클라이언트와의 연결이 닫히는 순간에는 메모리와 같은 리소스도 다시 돌아오겠죠.

이 시스템의 장점은 CPU 집약적(intensive) 어플리케이션에 뛰어나다는 겁니다. CPU 집약적(intensive) 명령어들은 많은 로직과 컴퓨팅할 시간을 필요로 합니다. 모든 새로운 요청이 새로운 스레드들에 의해 처리되면, 메인 스레드는 매우 큰 작업들로부터 벗어나게 됩니다. 그로 인해 메인 스레드는 시스템을 빠르게 만들고 중대한 연산을 할 수 있게 됩니다.

메인 스레드와 동떨어져 코드들이 실행되기 때문에 꽤 효율적이어보이지 않나요? 과연 이것보다 더 좋은 시스템이 있을까요?

Node.js 등장

우리가 루비 온 레일즈에서 돌아가는 멀티 스레드 지원 서버를 가지고 있다고 상상해봅시다. 그 서버는 서버에서 파일을 읽습니다. 그리고 요청한 브라우저에 전송합니다. 루비는 파일을 읽고 내용을 반환하기 위해 파일 시스템(file system)에게 요청을 전달할 것입니다. 파일 시스템은 컴퓨터에 데이터를 저장하고 불러오는데 사용됩니다.

요점은 루비가 파일 시스템의 작업이 끝날 때까지 아무것도 안한다는 것입니다. 그리곤 루비는 내용을 수집하고 그 내용을 브라우저로 보냅니다.

여기가 노드 js가 등장하는 곳입니다. 노드 js에서는 파일 시스템이 파일을 읽는 동안 노드는 남는 시간을 다른 요청을 처리하는데 사용합니다. 파일 시스템의 작업이 끝나게 되면, 파일 시스템은 노드에게 리소스를 가져가서 브라우저에게 뿌리라고 합니다. 이것은 노드 js의 event loop 때문에 가능한 일입니다.

노드 js는 자바스크립트와 이벤트 루프로 만들어졌습니다.

nodejs-architecture.png

image source is from https://codeburst.io/the-only-nodejs-introduction-youll-ever-need-d969a47ef219

이벤트 루프는 말하자면 이벤트를 기다렸다가 전송해주는 프로그램입니다. 다른 중요한 사실 하나는 자바스크립트는 싱글스레드라는 것입니다. 노드도 그렇습니다.

우리가 식당으로 예를 들었던 게 기억나나요? 노드는 몇명의 손님이 있냐에 상관 없이 1명의 종업원만 있는 겁니다.

각각 요청에 각 1개의 스레드를 요구하는 다른 언어들과는 다르게 노드는 모든 요청을 받고 난 후에 대부분의 작업을 다른 시스템 워커들에게 부여합니다. OS커널로부터 도움을 받아 이런 일들을 효율적으로 처리하는 Libuv라고 불리는 라이브러리가 있습니다. 백그라운드 워커들이 작업을 완료했을 때, 해당 이벤트로 등록된 노드JS 콜백에 이벤트를 전달합니다.

여기서 콜백(callbacks)이란 개념이 소개되었습니다. 콜백은 다른 함수에 인자로 던져지는 녀석들입니다. 특정한 조건이 충족됐을 때 호출됩니다.

노드 js 개발자들이 일반적으로 하는 일은 특정한 노드 이벤트가 발생했을 때 불러지는 이벤트 핸들러를 작성하는 것입니다.

노드 js는 하나의 스레드만 갖고도 멀티 스레드 시스템보다도 훨씬 빠릅니다.

그 이유는 프로그램이 많은 시간이 필요한 숫자, 산술, 논리 연산만으로 구성되지 않기 때문입니다. 사실, 많은 시간동안 프로그램은 파일 시스템에 무언가를 쓰는데 보내기 때문입니다. 또, 네트워크 요청을 수행하거나 외부 장치, 콘솔에 접근하는데도 많은 시간을 보냅니다. 노드는 이런 상황에서 장점을 발휘합니다. 노드가 그런 상황에 맞닥뜨렸을 때는 그 일을 빠르게 처리할 다른 누군가에게 보내고 들어오는 다른 요청들을 처리합니다.

이전에 배웠던 것을 전부 기억한다면, 노드가 CPU를 소비하는 연산에는 강하지 않다는 사실을 추측할 수 있을 것입니다. CPU 집약 명령들은 메인 스레드에 지나친 부하를 겁니다. 싱글 스레드 시스템을 사용할 때는, 메인 스레드가 다른 작업을 할 수 있게 메인 스레드에 너무 많은 부하를 거는 작업을 피하는 것이 이상적입니다.

이것 역시 반드시 알아둬야 하는데 자바스크립트 내부의 모든 것은 우리가 작성한 코드만 빼고는 평행적으로 작동합니다. 맞습니다. 우리가 작성한 코드만 한 번에 하나씩 실행됩니다. 다른 워커들이 작업을 동시적으로 할 때도요.

당신이 100% 이해하는 걸 돕기 위해 비유를 하나 더 해봅시다.

한 왕이 수천명의 부하들을 거느리고 있는 것을 상상해보세요. 왕은 부하들이 했으면 하는 것들의 리스트를 적고있습니다. 물론 그 리스트는 상당히 길겠죠. 부하는 리스트를 받고 다른 부하들에게 일을 위임합니다. 한 명의 부하가 일을 끝냈을 때, 부하는 자기가 일을 한 결과를 왕에게 보고합니다. 왕은 부하들이 일하는 동안 항상 다른 리스트를 만드느라 바쁘니까 왕은 결과를 모으고 부하에게 다른 할 일을 줍니다.

여기서 요점은 만일 많은 것들이 병렬로 처리된다 하더라도, 왕은 한 번에 한 가지의 일만 한다는 것입니다. 이 비유에서 당신의 코드는 왕입니다. 그리고 부하들은 노드JS 백그라운드 워커들이죠. 당신의 코드만 빼고는 모든 것들이 병렬로 일어납니다.

그럼 계속 배워볼까요?

노드 js로 웹앱 작성해보기

노드로 웹앱을 작성한다는 것은 노드에 특정 이벤트가 발생했을 때, 이벤트를 처리하는 이벤트 핸들러를 작성하는 것입니다. 예제를 봅시다.

  1. 명령 프롬프트나 터미널에서 새 폴더를 만들고, 폴더를 열어봅시다.
  2. npm init을 입력하고 엔터를 치세요. 루트 폴더에 package.json이라는 파일이 생길 때까지 기다리세요.
  3. server.js라는 파일을 폴더 안에 만들어보세요. 그리고 그 안에 다음 코드를 입력해봅시다.
// server.js
const http = require('http'),
      server = http.createServer();


server.on('request', (request, response) => {
    response.writeHead(200, {'Content-Type':'text/plain'});
    response.write('Hello World');
    response.end();
});

server.listen(3000, () => {
    console.log('Node Server Created at port 3000');
});
  1. 명령 프롬프트에서 node server.js를 입력하고 엔터를 쳐보세요. 아마 다음과 같은 문자들이 콘솔에 나타날 것입니다.
node server.js
// Node server started at port 3000

브라우저를 열고 주소 입력창에 localhost:3000을 입력해보세요. Hello world라는 메시지가 보일 것입니다.

첫번째 줄에서 우리는 http모듈을 require함수로 불러왔습니다. 이 모듈은 http 오퍼레이션들을 다루기 위한 인터페이스를 제공합니다. 우리는 createServer() 메소드를 불러왔고 서버를 만들었습니다.

그 다음에 우리는 서버 객체 내 requeston이벤트에 대한 이벤트 핸들러를 만들었습니다. 이 콜백 함수는 2개의 객체를 인자로 받습니다. requestresponse 객체입니다. 이 두 객체는 들어오는 요청(request)과 응답(response)에 대한 정보를 갖고 있습니다.

우린 노드가 우리에게 맞추어 동작하게 만들 수도 있습니다.

// server.js
const http = require('http'),

server = http.createServer((request, response) => {
    response.writeHead(200, {'Content-Type':'text/plain'});
    response.write('Hello World');
    response.end();
});

server.listen(3000, () => {
    console.log('Node Server Created at port 3000');
});

여기서는 createServer() 메소드에 인자로 콜백함수를 넘겼습니다. 노드는 우리에게 맞추어 request 이벤트 핸들러를 추가합니다. 여기서 우리가 고려할 것은 오직 requestresponse 객체 뿐입니다.

우리는 서버 응답에 헤더(주로 상태 코드와 콘텐츠 타입)를 추가하기 위해서 response.writeHeader() 메소드를 이용할 수도 있습니다. response.write()메소드는 웹페이지에 무언가 내용을 쓰기 위해 이용됐었죠. 이 때 우린 reponse.end() 메소드를 이용하여 서버에 대한 응답을 닫아볼 것입니다.

마지막으로, 우리는 서버에게 포트 3000번을 감시(listen)하라고 시킬 것입니다. 우리가 우리 로컬 머신에서 개발하는 동안 우리 웹페이지의 데모를 볼 수 있도록 말이죠. server.listen() 메소드는 두번째 인자로 콜백함수를 받습니다. 이 콜백 함수는 서버가 시작하자마자 실행됩니다.

노드 콜백과 친해지기

노드는 단일 스레드 이벤트 환경입니다. 이 말은 모든 것들이 이벤트를 통해 응답 한다는 것이죠. 콜백은 어떠한 이벤트가 발생했을 때, 다른 함수의 인자로 전달되는 함수라고 보면 됩니다.

다음은 그 예제입니다.

// server.js
const http = require('http'),
      
makeServer = function (request, response) {
	response.writeHead(200, {'Content-Type':'text/plain'});
	response.write('Hello world');
	response.end();
},
      
server = http.createServer(makeServer);

server.listen(3000, () => {
	console.log('Node server created at port 3000');
});

자바스크립트에서 함수는 1급 객체이기 때문에, 위의 소스에 있는 MakeServer는 콜백입니다. 콜백은 변수의 형태로도 전달될 수 있고 다른 함수로 전달될 수도 있습니다.

만일 자바스크립트에 친숙하지 않다면, 이벤트 프로그래밍에 친숙해지는데는 좀 시간이 걸릴 것입니다.

만일 우리가 조금 어렵거나 복잡한 자바스크립트 코드를 작성하기 시작했다면, 우리는 자바스크립트 콜백 지옥에 빠질 수 있습니다. 콜백 지옥은 너무 많고 깊은 내부 함수가 있어 코드가 점점 읽기 어려워지는 지점을 말합니다. 콜백 지옥이 일어났을 때, 우리는 콜백을 대체할 더 효율적이고 진보된 방법을 찾아야 할 수도 있습니다. promises라는 개념을 알아보세요. Eric Elliot이 쓴 훌륭한 가이드가 있습니다. 프로미스에 대해 공부하려면 다음 링크부터 읽어보기 시작하는 것은 나쁘지 않은 선택입니다. 자바스크립트 면접 마스터하기 : 프로미스(Promises)란 무엇인가?

노드의 라우팅

서버는 많은 파일을 저장합니다. 브라우저는 요청 시에 서버에게 브라우저가 무엇을 찾는지 알려줍니다. 서버는 그들이 요청한 파일을 줌으로써 응답하는데, 이를 라우팅(Routing)이라 합니다.

노드 js에서는 우리가 수동으로 라우터를 정의해야 합니다. 이건 별로 크거나 어려운 일은 아닙니다. 기본 코드를 살펴봅시다.

// server.js
const http = require('http'),
      url = require('url'),
// making callback function in a variable
makeServer = function (request, response) { 
	let path = url.parse(request.url).pathname;
	console.log(path);
	if(path === '/') {
		response.writeHead(200, {'Content-Type': 'text/plain'});
		response.write('Hello world');
    }
	else if(path === '/about') {
		response.writeHead(200, {'Content-Type': 'text/plain'}); 
		response.write('Blog page');
    }
	else {
		response.writeHead(404, {'Content-Type': 'text/plain'});
		response.write('Error Page');
    }
	response.end();
},
server = http.createServer(makeServer);
server.listen(3000, () => {
	console.log('Node server created at port 3000');  
});

이 코드를 server.js 파일에 붙여넣고 한번 훑어보세요. 이제 node server.js명령어를 통해 실행해보고 브라우저 주소에 localhost:3000이나 localhost:3000/about 같은 주소를 쳐보세요. 그리고 localhost:3000/somethingelse 와 같이 뒤에 아무거나 입력해서 에러메시지도 한번 살펴보세요. 맞게 나오나요?

이렇게 우리는 즉시 서버를 만들어서 실행해볼 수 있었지만, 서버 내에 모든 웹페이지마다 이런식으로 코드를 써주는 것은 정말 미친짓입니다. 아무도 이렇게 하진 않습니다. 그냥 단순히 서버가 어떻게 구성되는지에 대해서 알기 위해서만 이렇게 작성해본 것입니다.

보았을지 모르겠지만, 우리는 url이라는 새로운 모듈을 불러왔습니다.이 모듈을 이용하여 우리는 url을 쉽게 다룰 수 있습니다.

.parse()라는 메소드는 인자로 url을 받아서 protocol, host, path, querystring 과 같은 부분으로 나눕니다. 만일 저 부분들이 의미하는 곳을 지금 당장 모르더라도 괜찮습니다.

예제를 봅시다.

dividedURL.png

우리가 url.parse(request.url).pathname을 입력하면, url 모듈은 우리에게 pathname 부분을 줍니다. 모듈은 주로 우리가 라우트 요청을 수행하기 위해 사용할 url을 줍니다.

익스프레스(Express)를 이용해 라우팅하기

만일 당신이 미리 공부를 좀 해봤다면, 익스프레스에 대해 분명 들어봤을 것입니다. 익스프레스는 노드js 프레임워크로 웹앱을 빌드하거나 API를 만들기 위한 것입니다. 노드js 앱을 만들고 싶다면 익스프레스도 필요할 것입니다. 익스프레스는 모든 것들을 쉽게 만들어줍니다. 이제 잠시 후면 왜 그런지 알게 될 것입니다.

터미널이나 명령프롬프트에서 root 폴더로 가셔서 npm install express --save를 입력하세요. express 패키지를 설치하기 위한 명령어입니다. 우리 프로그램에서 Express를 실행하기 위해서는 require 함수로 불러오는 것도 필요합니다.

const express = require('express');

우리의 삶은 더욱 편해집니다.
익스프레스를 이용해 기본적인 라우트를 작성해봅시다.

const express = require('express'),
      server = express();

server.set('port', process.env.PORT || 3000);

//Basic routes
server.get('/', (request, response) => {
	response.send('Home page');  
});

server.get('/about', (request, response) => {
	response.send('About page');
});

// Express error handling middleware
server.use((request, response) => {
	response.type('text/plain');
	response.status(505);
	response.send('Error page');
});

// Binding to a port
server.listen(3000, () => {
	console.log('Express server started at port 3000');  
});

아까보단 조금 깔끔하죠? 점점 익숙해지고 있을 거라고 생각합니다. express 모듈을 추가한 이후에는 우리는 함수를 이용하여 간단하게 작성할 수 있게 됐습니다. 서버 프로그래밍은 이제 시작입니다.

다음으로, 우리는 포트를 server.set() 메소드를 이용하여 작성하고 있습니다. process.env.PORT는 앱이 돌아가는 환경의 포트를 가져옵니다. 사용이 불가능 할 때, 우리는 3000번을 기본 포트로 세팅합니다.

위쪽 코드를 잘 관찰하셨다면, 익스프레스에서 라우팅을 할 때 다음과 같은 패턴을 가진다는 것을 눈치챘을 것입니다.

server.VERB('route', callback);

VERB자리에 들어갈 것은 GET, POST와 같은 것들입니다. pathname은 도메인에 추가되는 문자열입니다. 그리고 콜백은 요청이 들어왔을 때 우리가 실행시키길 원하는 어떤 함수입니다.

다음으로 알아야 될 것은

server.use(callback)

익스프레스에서 .use()라는 메소드의 정체는 미들웨어(middleware)입니다. 미들웨어는 우리에게 무거운 http를 들어주는 역할을 합니다. 위의 코드에서는 미들웨어가 에러 핸들링을 해줍니다.

마지막으로, 우리가 이전에 작성 했던 것과 같은 server.listen()이 있습니다. 기억하시죠?

노드 js에서 라우팅을 할 때는 이렇게 하시면 됩니다. 이제 우리는 노드에서 쓰는 데이터베이스를 알아볼 것입니다.

노드 js 내 데이터베이스

자바스크립트를 모든 일에 사용한다는 아이디어를 사랑하는 사람들이 많습니다. 바로 적용할 수 있는 몇몇 데이터베이스를 나열해보자면 MongoDB, CouchDB와 같은 것들이 있습니다. 이 데이터베이스들은 모두 NoSQL 데이터베이스입니다.

NoSQL 데이터베이스는 키/값 쌍 형식으로 구성돼있습니다. 문서(Document) 기반이고 데이터는 테이블형태로 저장되지 않습니다.

일단 몽고DB부터 살펴봅시다. 몽고DB는 도큐먼트 기반의 NoSQL 데이터베이스입니다. 만일 MySQL, SQL server와 같은 관계형 데이터베이스만 써봤다면, 데이터베이스, 테이블, 로우, 컬럼 등의 개념에 정통해야 합니다. 사실 몽고 DB와 관계형 DB가 완전히 다른 것은 아닙니다.

몽고DB 공식 사이트에서 몽고DB와 관계형DB의 비교 포스팅을 구경해봅시다. 링크

데이터들을 깔끔하게 잘 정리하기 위해서 도큐먼트들을 몽고에 저장하기 전, 스키마를 강요하고 기본 타입을 체크하고 유효성 검사를 하는 Mongoose를 사용하는게 좋을 것입니다. Mongoose는 몽고와 노드 사이의 미들웨어로써 동작합니다.

몽고를 시작하기 위해서, 공식 몽고 사이트로 가서 초록색 다운로드 버튼을 누르세요. 커뮤니티 서버를 찾아 운영체제와 스펙을 선택하세요.

이 포스트는 정말 깁니다. 짧고 분리된 포스팅 작성을 위해, 몽고 공식 인스톨 가이드를 참조해주세요.

몽고를 시작하기 위해서 몽구스와 함께하는 몽고DB 노드js 앱 쉽게 개발하기를 읽어보세요. 몽고를 시작하기 위한 가장 기초적인 지식을 제공합니다.

노드와 익스프레스를 이용해 RESTful API 만들기

API는 웹 어플리케이션이 다른 곳으로 데이터를 보내기 위한 것입니다. 회원 가입이나 로그인을 할 때 페이스북 계정을 이용하는 웹사이트를 방문해본적이 있나요? 페이스북은 다른 어플리케이션들이 사용할 수 있는 페이스북 내부 기능들을 제공합니다.

RESTful API는 서버 또는 클라이언트가 서로의 상태에 대해서 모르는 것입니다. REST 인터페이스를 이용하여, 각각 다른 클라이언트들이 각각의 상태를 신경쓰지 않고 같은 REST 엔드 포인트에 접근하고, 같은 동작을 하고 같은 응답을 받습니다.

API 엔드 포인트란, API 내부에서 값을 리턴하는 하나의 함수입니다.

RESTful API를 만드는 것은 JSON 또는 XML 포맷으로 데이터를 보내는 것도 포함됩니다. 노드 js로 하나 만들어봅시다. 클라이언트가 Ajax를 통한 요청을 했을 때, 더미 제이슨 데이터를 리턴하는 것을 만들어봅시다. 이건 대단한 API는 아니지만 노드에서 API가 어떻게 작동하는지 알아보는데 매우 도움이 될 것입니다.

  1. node-api라는 폴더를 하나 만드세요.
  2. 폴더 내부로 가서 npm init명령어를 입력하세요. 그럼 우리의 모든 의존성을 포함하는 파일 하나가 만들어질 겁니다.
  3. npm install --save express를 쳐서 익스프레스를 설치하세요.
  4. 루트 디렉토리 내부에 server.js, index.html, user.js 3개의 파일을 만드세요.
  5. 다음 코드를 각각 파일에 복사하세요.
//user.js
module.exports.users = [
	{
      name: 'Mark',
      age: 19,
      occupation: 'Lawyer',
      married: true,
      children: ['John', 'Edson', 'ruby']
    },
	{
      name: 'Richard',
      age: 27,
      occupation: 'Pilot',
      married: false,
      children: ['Abel']
    },
	{
	  name: 'Levine',
	  age : 34,
	  occupation: 'Singer',
	  married : false,
	  children : ['John','Promise']
	},
	{
	  name: 'Endurance',
	  age : 45,
	  occupation: 'Business man',
	  married : true,
	  children : ['Mary']
	}
]

이건 우리가 다른 앱들과 공유하고 싶은 데이터들입니다. 다른 프로그램들이 이걸 쉽게 이용할 수 있도록 만들기 위해 우리는 이것을 추출(export)할 것입니다. 이게 핵심 아이디어입니다. 우리는 users 배열을 modules.export 객체에 저장할 것입니다.

//server.js
const express = require('express'),
      server = express(),
      users = require('./users');

//setting the port.
server.set('port', process.env.PORT || 3000);

//Adding routes
server.get('/', (request, response) => {
	response.sendFile(__dirname + '/index.html');
});

server.get('/users', (request, response) => {
	response.json(users);  
});

//Binding to localhost://3000
server.listen(3000, () => {
	console.log('Express server started at port 3000');
});

여기서 우리는 require('express') 이후에 express() 함수를 통하여 우리 서버를 만들었습니다. 유심히 보았다면, 우리가 다른 무언가를 더 요청한 것이 보일 것입니다. 그게 바로 users.js입니다. 우리가 공유하려는 데이터를 저장했던 것을 기억하시나요? 프로그램이 작동하기 위해 이 데이터가 필요합니다.

익스프레스는 특정 내용을 브라우저로 전송하는데 우리를 도와주는 메소드들을 가지고 있습니다. response.sendFile()은 파일을 찾아 브라우저로 보냅니다. 우리는 여기서 ___dirname을 사용하는데, 이 의미는 우리 서버가 동작하는 위치를 말합니다. 그 때, 우리는 우리가 올바른 파일을 목표로 하고 있다는 것을 확실히 하기 위해 + 'index.html'을 붙입니다.

response.json()json 내용을 요청한 웹사이트로 보냅니다. 우린 그 내용, 우리가 실제로 공유하는 users 배열을 인자로 줍니다. 나머지는 아마 많이 본 내용일 것입니다.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Home page</title>
  </head>
  <body>
    <button>Get data</button>
    <script src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
    <script type="text/javascript">
      const btn = document.querySelector('button');
      btn.addEventListener('click', getData);
      function getData(e) {
      	$.ajax({
      		url: '/users',
      		method : 'GET',
      		success : function(data) {
      			console.log(data);
      		},
      		error: function(err) {
      			console.log('Failed');
      		}
      	})
      }
    </script>
  </body>
</html>

node server.js로 노드 서버를 실행시켜보세요. 그 다음 브라우저를 열고, localhost:3000에 접속하고 버튼을 누른 뒤에 브라우저 콘솔을 살펴보세요.

ajax-calls-api.gif

btn.addEventListener('click', getData);에서 getDataGET AJAX 요청을 만듭니다. 이 요청은 url, success, error와 같은 특정 파라미터를 세팅하는 $.ajax({properties}) 함수를 내포하고 있습니다.

실제 존재하는 시스템에서는 우리는 단순히 JSON을 읽지 않을 것입니다. 아마 우리는 데이터를 CRUD(Create, Read, Update, Delete)하고 싶어 하겠죠. 익스프레스는 http 동사들(POST, GET, PUT, DELETE)을 이용해 우리가 CRUD를 할 수 있게 만들어 줄 것입니다.

Express를 이용해 API를 만드는 것을 더 알고 싶다면 이 링크를 읽어보세요. 아마 도움이 될 것입니다.

노드JS 소켓으로 네트워킹하기

컴퓨터 네트워크는 정보를 주고 받기 위해서 컴퓨터를 연결하는 것을 말합니다. 노드js에서 네트워킹을 하기 위해 우리는 net모듈을 요청(require)할 수 있습니다.

const net = require('net')

Transmission Control Protocol(TCP)에서는 2개의 엔드포인트가 필요합니다. 하나가 포트와 연결하는 동안 다른 하나의 엔드포인트는 어떠한 숫자 포트에 종속(bind)되어 있습니다.

만일 이해가 안간다면, 다음 비유를 한번 읽어보세요.

당신의 휴대폰을 상상하세요. 일단 유심칩을 한번 사면, 우리는 어떤 번호를 부여받고 그 번호에 종속(bind)됩니다. 우리의 친구가 전화하기를 원할 때, 친구들은 우리의 번호로 연결합니다. 이 경우에는 우리의 번호가 1개의 엔드포인트고 전화를 건 친구가 또 다른 엔드포인트죠.

아마 지금쯤 이해 했겠죠?

이 지식들을 결합시키기 위해서, 우리는 파일이 변경됐을 때, 파일을 지켜보고(watch) 연결된 클라이언트에게 정보를 전송(inform)하는 프로그램을 만들어봅시다.

  1. node-network라는 폴더를 하나 만들어봅시다.
  2. filewatcher.js, subject.txt, client.js라는 3개의 파일을 만들어봅시다.

다음 내용을 filewatcher.js에 넣어보세요.

//filewatcher.js
const net = require('net'),
      fs = require('fs'),
      filename = process.argv[2],
      
server = net.createServer((connection) => {
	console.log('Subscriber connected');
	connection.write(`watching ${filename} for changes`);

	let watcher = fs.watch(filename, (err, data) => {
		connection.write(`${filename} has changed`);  
	});

	connection.on('close', () => {
		console.log('Subscriber disconnected');
		watcher.close();
    });
});

server.listen(3000, () => console.log('listening for subscribers'));
  1. 파일을 감시(watch)하려면 감시할 파일을 줘야겠죠? 다음 내용을 subject.txt를 넣고 저장하는 것을 잊지 맙시다.
Hello world, I'm gonna change
  1. 클라이언트를 만들어봅시다. 다음 내용을 client.js에 넣어보세요.
const net = require('net');
let client = net.connect({port:3000});

client.on('data', (data) => {
	console.log(data.toString());
});
  1. 우리는 두 개의 터미널이 필요합니다. 첫번째에서는 다음과 같이 감시할 파일이름을 인자로 주어 filename.js를 실행할 겁니다.

참고 : //로 표기된 부분은 치지마세요. 그냥 추가적인 정보를 나타내려 한겁니다.

// the subject.txt will get stored in our filename variable

node filewatcher.js subject.txt

// listening for subscribers

다른 터미널에서는 client.js를 실행시켜주세요.

node client.js

// watching subject.txt for file changes.

이제 subject.txt 파일의 내용을 바꿔봅시다. 바꾼 후에는 저장하는 것 잊지마세요. client.js를 실행시킨 터미널을 보시면 다음과 같은 메시지를 확인할 수 있을 것입니다.

//subject.txt changed.

네트워크의 큰 특징 중 하나는 많은 클라이언트들이 동시에 연결할 수 있다는 것입니다. 다른 터미널을 더 띄워서 node client.jsfilewatcher.js에 많이 연결해보세요. 그리고 subject.txt의 내용을 변경해보세요. (저장하는 것 잊지말고요)

우리가 한 것은 무엇인가?

여태까지 한 것을 정확히 이해하지 못했어도 걱정마세요. 이제부터 훑어볼 겁니다.

filewatcher.js가 한 것들은 기본적으로 3가지 일로 볼 수 있습니다.

  • net.createServer()메소드를 이용하여 많은 클라이언트들에게 메시지를 보내기 위한 서버를 만들었다.
  • console.log()connection.write()메소드를 이용하여 서버에게 클라이언트가 연결됐다고 알려주고, 클라이언트에게 감시 중인 파일이 있다고 알려주었다.
  • watcher 변수로 파일을 감시하고 클라이언트의 연결이 끊어지면 열었던 연결을 닫았다.

filewatcher.js의 소스를 다시 한번 봅시다.

//filewatcher.js
const net = require('net'),
      fs = require('fs'),
      filename = process.argv[2],
      
server = net.createServer((connection) => {
	console.log('Subscriber connected');
	connection.write(`watching ${filename} for changes`);

	let watcher = fs.watch(filename, (err, data) => {
		connection.write(`${filename} has changed`);  
	});

	connection.on('close', () => {
		console.log('Subscriber disconnected');
		watcher.close();
    });
});

server.listen(3000, () => console.log('listening for subscribers'));

우리는 2개의 모듈을 요청(require) 하였습니다. fsnet 모듈을 각각 파일을 읽고 쓰기 위해 그리고 네트워크 연결을 구성하기 위해 사용했습니다. 자세히 관찰했다면, process.argv[2]의 존재를 확인했을 것입니다. 프로세스는 실행 중인 노드js 코드에 대한 필수적인 정보를 제공하는 전역 변수입니다. argv[]는 인자들의 배열입니다. argv[2]라고하면, 노드js 코드를 실행하기 위해 사용됐던 세번째 인자정보를 참조할 것입니다. 명령어 줄에서, 우리는 우리가 감시하고 싶은 파일의 이름을 3번째 인자(argv[2])로 적었습니다. 아래 명령어를 기억하시나요?

node filewatcher.js subject.txt

또 어디서 많이 본 메소드가 있는데 net.createServer() 이 메소드는 클라이언트가 포트에 연결됐을 때 콜백함수를 받아들여 실행(fire)됩니다. 콜백은 클라이언트와 통신하는 객체인 단 하나의 파라미터만 가지고 있습니다.

connection.write() 메소드는 포트 3000에 연결된 클라이언트에게 데이터를 보냅니다. 이 경우에는 net.createServer()의 콜백 파라미터였던 connection 객체입니다. 클라이언트에게 파일이 감시되고 있다고 말합니다.

watcherclient에게 감시하는 파일이 변하는지 정보를 보내는 함수를 갖고 있습니다. 그리고 client는 프로세스에서 탈출하며 연결을 닫습니다. onclose 이벤트가 동작(fire)하고 이벤트 핸들러는 서버에 메시지를 보내고 watcher를 닫습니다.

//client.js

const net = require('net'),
      client = net.connect({port:3000});

client.on('data', (data) => {
	console.log(data.toString());
});

client.js는 꽤 직관적입니다. net 모듈을 요청하고 모듈을 포트 3000번과 연결시킵니다. 그리고 그 때 우린 data 이벤트를 기다리고(listen) 데이터로 온 것을 콘솔에 로그를 출력합니다.

이건 그냥 네트워크 동작 탐구 중 수박 겉햝기입니다. 주로, 주로 어떤 이벤트가 일어날 때, 한 엔드 포인트에서 메시지를 방송하고 모든 연결된 클라이언트가 메시지를 data 이벤트의 형태로 받는 거죠.

노드에서의 네트워킹에 대해서 심화적으로 알고 싶다면, 노드의 공식문서가 여기 있습니다. 노드를 이용해 TCP 서비스를 구축하는 방법도 읽어보세요.

정말 많은 내용이었습니다.

만일 노드js를 이용해서 확장 축소 가능한 (scalable) 웹 앱을 만들고 싶다면, 그냥 서버를 작성하고 express로 라우팅하는 것보단 많은 것을 알아야 합니다.

여기 제가 추천하는 책들입니다.

  • NodeJs the right way
  • Web Development with node and express
profile
풀스택 웹개발자로 일하고 있는 Jake Seo입니다. 주로 Jake Seo라는 닉네임을 많이 씁니다. 프론트엔드: Javascript, React 백엔드: Spring Framework에 관심이 있습니다.

0개의 댓글