[Node.JS] 노드는 무엇이고 어떠한 기능들이 있는가? — (1)

JeungJoo Lee
Day34 Inc.
Published in
23 min readJan 29, 2019

안녕하세요. 캡틴체인입니다!

Day34 기술 블로그에 제가 공부하거나 관심있는 분야에 대한 정리를 하고자 열심히 글을 올리고 있습니다. 지금까지 대부분 블록체인에 관련된 기술만 포스팅을 했었는데요.

오늘은 제가 프로젝트를 진행하면서 조금 더 Back to the basic 으로 돌아가고자 Node.js 를 공부하고 있습니다. 사실 모두가 자바스크립트 언어는 다를 수 있는 언어다 라고 말들은 많이 하지만 실제로 제대로 알고 있는 사람은 몇 안되는 것 같습니다. 저 조차도 그러니까요 ㅠ… 자바스크립트는 사실 Node.JS 가 나오면서 강력한 언어로 각광 받고 있는데요.. Node.JS 로 인해 별볼일 없는 자바스크립트 언어에서 굉장한 언어로 거듭났습니다.

저도 과거에는 자바스크립트보다 Java 언어로 시작해 EJB , Struts, Spring Framework 에 이르기까지 Java 개발자에 가까웠고 서버도 Apache Tomcat 이나 Jeus , Weblogic 등의 WAS를 주로 사용하였습니다. 실무에서 몇 번 Node.JS 를 접하고 나서 매력에 빠졌고 지속적으로 공부 해야겠다는 생각 때문에 지금도 불철주야 기술 스터디 중에 있습니다.

오늘은 첫 번째로 노드 노드 하는데 노드가 무엇인지와 어떠한 기본 모듈 기능들이 있는지 살펴보도록하겠습니다.

Node.JS 는 무엇인가?

Java 언어가 모든 OS 운영체제에서 Virtual Machine 환경 안에서 Runtime 이 구동 되듯이 Node.JS 는 웹브라우저에 종속적인 자바스크립트에서 외부에서 실행할 수 있는 Runtime 환경을 Chrome V8 엔진을 제공하여 여러 OS 환경에서 실행할 수 있는 환경을 제공하게 됩니다. 이것을 Node.JS 라고 정의할 수 있습니다.

호출 스택과 이벤트 루프

본인도 자바스크립트를 코딩한다고 했지만 이벤트 루프에 대해서 정확히 설명할 수 없었습니다. 그래서 이번 기회에 공부를 하며 알게 되었는데요. 우선 아래 코드를 보며 설명을 드려보겠습니다

function first() {
second()
console.log('첫 번째 실행')
}
function second() {
third()
console.log('두 번째 실행')
}
function third() {
console.log('세 번째 실행')
}
first()

console 로 찍히는 순서는 어떻게 되는가 에 대한 문제입니다.. 답은 무엇일까요?

답은 세 번째, 두 번째, 첫 번째 실행으로 실행 됩니다. 이 부분의 실행 순서를 이야기 하기 위해선 호출 스택의 자료 구조로 first(), second(), third() 가 메모리에 들어가게 되어 있습니다. 스택의 특징으로는 Last In First Out(후입선출) 의 특징을 가지고 있습니다.

위의 코드는 first, second, third 함수가 정의 되어있고 first 함수 부터 호출을 합니다. 체이닝으로 실행이 연결되어 있고 first가 먼저 호출 되어있지만 second 함수는 first 함수에서 호출되고 second 함수는 third 함수에서 호출되게 됩니다. third 함수는 console.log를 실행하게 되고 가장 윗 부분에 상주해 있다가 스택에서 빠져 나오게 됩니다. 그 다음 second 안에 있는 console.log를 실행하게 되고 최종적으로는 first의 console.log를 가장 나중에 실행하게 됩니다. 결과는 아래와 같습니다.

그렇다면 아래 코드는 어떤 순서대로 실행이 될까요?

function run() {
console.log(`3초 후 실행함`)
}
console.log(`시작`)setTimeout(run, 3000)console.log(`끝`)

정답은 아래와 같이 실행이 됩니다.

setTimeout은 시간을 지연시켜 함수를 실행하는 타이머 함수입니다. 시간은 정해져있지만 실제로 자바스크립트는 비동기 처리에 대한 함수가 많기 때문에 위의 예제를 통해 이벤트 루프를 설명하자면 이렇습니다.

실제로 동기적인 처리를 할 때는 자바스크립트는 컨텍스트 상 호출 스택 구조에서 가장 나중에 처리된 놈이 가장 먼저 처리 되는 현상을 볼 수가 있었습니다. 그 다음 console.log를 통해 찍어주는 단순한 키워드인 ‘시작' 과 ‘끝' 이후 setTimeout 이라는 타이머 함수를 통해 ‘3초 후에 실행함' 이라는 키워드가 가장 마지막에 동작하는 것을 보실 수 있었습니다. 이 때 비동기적인 결과에 대한 부분들은 태스크 큐라는 부분에 적재를 시키게 되고 큐는 자료구조상 First In First Out(선입선출) 특성을 가지고 있습니다.

이 때 3초 뒤에 스택으로 넘겨서 태스크가 실행이 되도록 하게 할 것이냐 라는 문제를 관리하는 놈이 바로 이벤트루프입니다. 이상 대략적인 스택, 태스크 큐 그리고 이벤트 루프의 설명이였습니다.

Node 모듈 시스템

프론트에서 대부분 <script src=’{소스위치}’> 자바스크립트 임포트 모듈을 통해 변수나 오브젝트를 불러오는 방법이 있었다면 Node.JS에서는 아래와 같이 다른 자바스크립트 파일에서 참조할 수 있습니다.

// 참조해야 하는 constants.js 파일module.exports = {
sayHello : 'Hello!',
name : 'CaptainChain'
}

위의 선언된 변수와 오브젝트들은 아래의 코드처럼 참조가 가능합니다.

// funcModule.jsconst { sayHello, name } = require('{파일위치}');console.log(`${name} 님 ${sayHello}`)

위의 방법과 같이 참조 할 수 있게 됩니다. 이 모듈 시스템의 기능을 가지고 자바스크립트에서 기능별 모듈화를 진행할 수 있게 됩니다.

Event Driven, 싱글 쓰레드, Non-Blocking I/O

이벤트 드리븐과 싱글 쓰레드 그리고 논블록킹을 간단히 정리하자면 아래와 같습니다.

  • 싱글 쓰레드란?

우선 자바스크립트의 경우 싱글 쓰레드로 처리하는 방식을 채택하고 있습니다. 실제로 처리해야 할 일은 많은데 한번에 하나의 일만 하고 있다는 것으로 생각하면 자바스크립트는 왜 멀티 쓰레드 방식이 아닐까에 대해서는 고민이 자연스럽게 됩니다. 추측으로는 OS 운영체제의 자원을 할당하고 관리하는 부분에서 자바스크립트의 태스크에 대한 제어가 비동기 처리 방식으로 인해 어렵지 않을까 생각됩니다. Node.JS 는 이러한 싱글쓰레드의 단점을 멀티쓰레드 방식과 비슷하게 같은 동작을 병렬로 처리할 수 있는 방법들로 개선하고 있습니다 (실제론 멀티 쓰레딩은 아니고 비슷하게 따라했다고 생각하면 될 것 같습니다)

  • 이벤트 드리븐이란?

위에서 설명한 개념 중 스택과 이벤트 루프 그리고 태스크 큐에 대한 개념에서 이벤트 드리븐은 내가 서비스하고 있는 하나의 사이트를 통해 기능별로 등록된 리스너들입니다. 위의 코드 처럼 콘솔로 바로 실행 이후 종료되는 프로그램이 아니라 언제 누가 내가 만든 사이트에 들어올지 모르는 상황에서 대기를 하고 있다는 것이죠. 이 때 이벤트별로 기능이 정의가 되게 되는데요. 예를 들어 방문했을 때 text/html contentType으로 사용자에게 페이지를 보여준다던지 게시물에 좋아요 버튼을 눌렀을 때 결과를 json 형식으로 클라이언트에게 보내줘서 사용자의 액션이 제대로 수행이 되었는지 사용자한테 알려줘야 겠지요. 이때 모든 일련의 이벤트들의 동작을 정의하고 등록된 상태가 이벤트 리스너에 등록된 상태입니다.

  • Non-Blocking I/O 란?

자바스크립트는 기본적으로 싱글쓰레드 방식을 채택중입니다. 이 때 비동기적 처리(Asyncronous Processing)의 태스크들을 호출 스택에서 태스크 큐로 보내거나 태스크 큐로 부터 이벤트 루프를 통해 다시 스택으로 가져오는 I/O의 형태를 Non-Blocking 이라고 하죠. 이에 따라 실행 순서에 영향을 미치는 행위를 Non-Blocking I/O 라고 간단히 말할 수 있을 것 같습니다. 반대로 Blocking 의 경우 동기적 처리(Syncronous Processing)들에 대해 뒤에 작업들이 해당 작업으로 인해 지연되는 현상을 이야기 합니다.

Node.JS 내장 객체

브라우저에서 내장객체가 window 객체가 있습니다. 그렇기 때문에 우리가 window를 생략하고 alert, confirm, setTimeout, href 등 내장 객체들을 사용할 수 있죠. 하지만 이 window 객체는 Node.JS 는 존재하지 않습니다. 다른 내장 객체가 존재하는데요. 바로 global 이라는 객체입니다. global은 이름을 명시적으로 사용해도 되지만 생략을 해도 가능합니다.

global 내장객체가 포함하고 있는 오브젝트들

위의 내장객체를 사용할 때는 global을 생략하거나 직접 명시적으로 붙일 수 있습니다. 예를들어, 우리가 잘 알고 있는 setTimeout을 사용하면 아래와 같이 사용가능 합니다.

// global을 앞에다 붙일 수 있음
global.setTimeout(()=>{
console.log(`say hello`)
},300)
// global을 생략하면 앞에 global이 있다는 것을 전제로 함
setTimeout(() => {
console.log(`say hello`)
}, 300)

Browser 의 내장객체인 window 객체와 비교해서 생각하면 편할 것입니다.

console 로그 객체

프로그래밍을 하다보면 디버깅에 대한 부분이 필수적입니다. 자바스크립트에서 디버깅하기 위해서 console 객체를 많이 사용했을 것이라 생각됩니다. 아래와 같이 console 객체가 지니고 있는 함수들의 목록은 아래와 같습니다. 실제로 dir, error, log, info 등을 대표적으로 사용하지만 그 외 많은 것들이 있습니다.

console 객체의 함수들

global 객체의 타이머 함수들

global 객체에 존재하는 타이머 함수들을 잠깐 살펴보면 아래와 같습니다.

  • setTimeout
  • setInterval
  • setImmediate
// 3초 후에 실행
const timeout = setTimeout(() => {
console.log('Function #1')
},3000)
// 3초 마다 실행함
const interval = setInterval(() => {
console.log(`Function #2`)
}, 3000)
console.log('setImmediate 전')
// 이벤트 루프로 보낼때 비동기로 할 때 사용함
const immediate = setImmediate(() => {
console.log(`setImmediate() 즉시 실행`)
})
console.log('setImmediate 후')
setTimeout(() => {
console.log(`현 시간부로 타임함수는 종료됨...`)
clearTimeout(timeout)
clearInterval(interval)
clearImmediate(immediate)
}, 7000)

위의 코드를 실행 했을 때의 결과는 어떨까요? 바로 아래와 같습니다.

setImmediate 함수는 바로 실행되는 함수입니다. 단, 비동기적으로 위에서 배웠던 이벤트 루프를 통해서 태스크 큐에 등록 되었다가 스택으로 불러와 실행하는 함수죠. 그래서 console.log 에 있는 내용들이 먼저 찍히고 나중에 setImmediate 함수가 실행이 됩니다.

setInterval은 정해진 시간인 3초마다 함수를 실행합니다. 취소하기 위해선 setInterval 시 리턴하는 객체 넘버를 통해 clearInterval 함수로 취소할 수 있습니다.

위의 타이머 함수들은 대부분 비동기적인 처리로 호출 스택에서 바로 처리하는 것이 아니라 이벤트 루프가 태스크 큐를 통해 관리하다가 호출 스택으로 보내는 방식으로 처리가 됩니다. 이 부분을 잘 머릿속으로 기억해야 나중에 자바스크립트 디버깅을 할 때 쉽습니다~

Node가 제공하는 내장 또 다른 객체

노드에서는 기본적으로 제공하는 또 다른 내장 객체들이 존재합니다. 바로, __filename ( 언더 스코어가 두 번 들어가 있음 )과 __dirname 그리고 process 입니다. __filename은 현재 파일 위치를 알 수 있으며 __dirname은 현재 경로를 알 수 가 있습니다. 그리고 process 내장 변수에는 여러가지 환경 설정에 대한 값 들과 서버 구동시간 서버 종료 함수 등 다양한 프로그램 정보들이 내장되어 있습니다. 위에서 언급했던 쓰레드보다 프로세스는 상위 개념이고 프로그램 단위라고 생각하시면 될 것 같습니다. 이러한 노드의 내부 정보들이 포함된 내장 객체들이 존재한다는 것 알아두시면 좋을 것 같습니다.

Node.JS 기본적으로 제공되는 모듈들

이제 마지막으로 Node.JS에서 기본적으로 제공되는 모듈들을 살펴보도록 하겠습니다. Node.JS 를 사용하는 사람들은 앱/웹 서비스를 만들기 위해 프로젝트의 Dependency 라는 라이브러리를 내 프로젝트에 추가하게 됩니다. 해당 명령어는 아래와 같이 추가할 수가 있죠

npm i "dependency 명" --save

위에서 추가된 Dependency들은 스크립트 파일안에서 require 나 import 를 사용하여 추가된 모듈안에서 Export 된 객체들을 사용할 수 있게 됩니다. 우선 우리가 살펴볼 모듈들은 Node.JS가 기본적으로 포함하고 있는 모듈들인데요. 어떠한 모듈이 존재하는지는 아래 Docs 에서 확인이 가능합니다.

OS, path, url 모듈 보기

우선 가볍게 처음에는 OS 모듈을 살펴보도록 할게요. https://nodejs.org/dist/latest-v10.x/docs/api/os.html

OS 모듈을 require하고 사용할 수 있는 함수들을 보면 아래와 같습니다. 대부분 내가 사용하는 OS 플랫폼과 메모리 그리고 OS 구동 시간등의 정보를 가져올 수 있는 모듈입니다.

const os = require('os')console.log('os.arch():', os.arch());console.log('os.platform():', os.platform());console.log('os.type():', os.type());console.log('os.uptime():', os.uptime());console.log('os.hostname():', os.hostname());console.log('os.release():', os.release());

아래 코드는 path 모듈을 사용한 것인데 현재 상대 경로나 절대 경로 표현에 의한 경로 추출이나 조합 파일들의 이름 및 확장자 추출 등 다양한 용도로 실무에서 사용 중입니다.

const path = require('path');const fileName = __filename;console.log('path.dirname():', path.dirname(fileName));console.log('path.extname():', path.extname(fileName));console.log('path.basename():', path.basename(fileName));console.log('path.basename():', path.basename(fileName, path.extname(fileName)));console.log('path.join():', path.join(__dirname, '..', '..', '/user', '.', '/doublej'));console.log('path.resolve():', path.resolve(__dirname, '..', 'user', '.', '/doublej'));

그리고 URL 컨텍스트에 대한 정보도 파싱해서 필요한 정보만 추출 할 수 있는 url 모듈도 아래와 같이 존재합니다.

const url = require('url');const { URL } = url;const myURL = new URL('http://www.naver.com?a1=1&a2=2&a3=3');console.log('new URL():', myURL);console.log('url.format():', url.format(myURL));console.log('------------------------------');const parsedUrl = url.parse('http://www.naver.com?a1=1&a2=2&a3=3');console.log('url.parse():', parsedUrl);console.log('url.format():', url.format(parsedUrl));

위의 URL 정보 중 GET Method 로 Request 를 받아들인 정보에서는 querystring 모듈을 사용하여 파라미터의 Key, Value 를 추출 할 수도 있습니다.

crypto 모듈 살펴보기

Node.JS 에서도 기본적인 암호화 모듈을 제공하고 있습니다. 암호화 방식은 복호화가 불가능한 단방향 암호화 방식과 암/복호화 가 가능한 양방향 암호화 방식 두 가지가 있습니다.

단방향 암호화

블록체인에서도 SHA3나 SHA256 등 주소나 블록을 구성하는 요소에 이 알고리즘들이 많이 쓰이죠. NodeJS 의 경우 crypto 모듈안에 이 메서드가 존재합니다. 아래와 같은 코드로 표현할 수 있는데요. 단순하게 1235와 1234의 숫자를 SHA512로 암호하하여 Hex(16진수)로 변환한 값이 아래와 같습니다. 해시 함수의 특성상 조그만한 변경이 일어났음에도 불구하고 결과는 어떠한 값이 변경되었는지 모를 정도로 확 바뀌어버리는 해시의 특징을 볼 수 있습니다.

console.log('hex(1235):', crypto.createHash('sha512').update('1235').digest('hex'));console.log('hex(1234):', crypto.createHash('sha512').update('1234').digest('hex'));
1235와 1234의 완벽히 달라진 해시 값

양방향 암호화

우선 AES-256-CBC 라는 암호화 알고리즘 기법을 이용하여 “1234” 라는 패스워드를 암호화 합니다. 그리고 복호화 할 때는 암호화된 값을 넣고 복호화를 하게 되는데요. 이 때 중요한 키는 바로 아래 Bold 체로 되어있는 ‘열쇠’ 라는 부분 입니다. 암/복호화를 진행할 수 있는 열쇠가 되는 것이죠

const crypto = require('crypto');
const cipher = crypto.createCipher('aes-256-cbc', '열쇠');let result = cipher.update('1234', 'utf8', 'base64');result += cipher.final('base64');console.log('암호화:', result);
const decipher = crypto.createDecipher('aes-256-cbc', '열쇠');
let result2 = decipher.update(result, 'base64', 'utf8');result2 += decipher.final('utf8');console.log('복호화:', result2);

util 모듈 살펴보기

util 모듈에는 우리가 콜백 지옥에서 벗어날 수 있는 Promise 를 반환하게 해줄 수 있는 promisify 라는 메서드가 존재합니다. 아래와 같이 util.promisify 를 통해 비동기 처리 Callback 함수를 프로미스로 반환하게 설정할 수 있습니다. 아래 코드처럼 crypto.randomBytes 콜백 함수는 promisify 등록 이후 then, catch 의 프로미스로 처리가 가능합니다.

const util = require('util');
const crypto = require('crypto');
const randomBytesPromise = util.promisify(crypto.randomBytes);randomBytesPromise(64)
.then((buf) => {
console.log(buf.toString('base64'));
})
.catch((error) => {
console.error(error);
});

이건 일반적인 개발을 할 때 많이 사용하지 않으나 프레임웍이나 기본 제공하는 라이브러리들을 사용하며 Deprecated 라는 용어를 많이 보았을 것입니다. 특정 라이브러리가 업데이트 되면서 현재 바로 사용을 못하는 것은 아니지만 향후에 없어질 것이라는 것을 암시하고 있는 것이죠. 아래와 같이 단순히 x, y 인자를 받아 더해주는 함수가 Deprecated가 될 것으로 가정 했을 때 util.deprecate 라는 함수를 사용하면 사용할 때마다 콘솔에 출력되는 것을 볼 수 있습니다.

const util = require('util');const dontUseMe = util.deprecate((x, y) => {
console.log(x + y);
}, 'dontUseMe 함수는 deprecated되었으니 더 이상 사용하지 마세요!');
dontUseMe(1, 2);

fs 모듈 살펴보기

fs 모듈은 file system 에 관련된 모듈입니다. 버퍼/스트림으로 파일을 읽어 복사를 하거나 생성/삭제 등 파일 입출력 관련한 많은 기능들을 담당하고 있고 파일을 읽는 코드는 아래와 같습니다. readme.txt 파일의 있는 내용을 읽어 출력하는 구문인데요. 이 때 처리는 Async로 처리가 됩니다. 만약 readFile 이 아닌 readSyncFile로 처리하게 된다면 Sync로 처리 됩니다. 단, 고려해야 될 사항은 싱글쓰레드의 특징을 가진 자바스크립트에서 빈번하게 파일을 읽게 된다면 Sync 하게 처리하는 것 보다 Async 하게 처리하는 것이 나을 수 있을 것 같습니다. 각 상황에 맞게 Async로 처리 할 것인지 Sync로 처리할 것인지를 잘 선택하셔야 합니다.

const fs = require('fs');fs.readFile('./readme.txt', (err, data) => {
if (err) {
throw err;
}
console.log(data);
console.log(data.toString());
});
readme.txt 파일을 읽어 출력한 모습

events 모듈 살펴보기

이벤트 모듈은 위에서 설명한 이벤트들 즉 클라이언트/서버 구조에서 어떤 사용자가 내가 운영하는 사이트에 들어와 좋아요나 댓글 포스팅을 했을 때 이벤트 리스너에 대기하고 있다가 이벤트에 대한 처리 결과를 사용자한테 넘겨줄 수 있는 모듈이라 말할 수 있습니다. 코드는 아래와 같습니다.

const EventEmitter = require('events');const myEvent = new EventEmitter();
const response = () => {
console.log('글 등록이 완성 되었습니다.');
}
myEvent.addListener('posting', () => { // 글 등록 로직
response();
});myEvent.emit('posting');

실제로 글 등록이 이루어지는 구문은 아닙니다. 위와 같이 addListner 나 on 을 통해 서비스 하고자 하는 기능에 대한 이벤트를 등록 할 수 가 있습니다. 이벤트를 등록할 때의 로직은 생략하고 response 는 단순하게 console 로 출력을 합니다. 이 ‘posting’의 이름을 가진 이벤트는 글등록에 이벤트로 이벤트 리스너에 등록이 되고 클라이언트가 서버에 요청할 때 사용자의 목적에 따라 emit 되어 실행이 되어지는 것이죠

예외처리(try catch) 하기

마지막으로 예외 처리입니다. 가장 좋은 것은 예외처리를 하는 것 보다 발생을 안하게 코드를 만드는 것이 가장 좋은 방법인데요.. 어쩔 수 없이 사용해야 하는 경우도 있죠. try catch 의 경우는 아래와 같이 설정할 수 있습니다. JAVA 와 다르게 자바스크립트의 경우 컴파일 시 에러를 Catch 한다고 하기보다 Runtime 시 에러를 Catch 하는 경우가 일반적인 상황 같습니다.

setInterval(() => {    console.log('시작');    try {       throw new Error('서버 에러');    } catch (err) {       console.error(err);    }}, 1000);console.log(process.argv);

위의 코드는 setInterval 이라는 타이머 함수를 통해 1초마다 지속적으로 throw로 강제 에러를 발생시키는 구문입니다. Syntax는 위와 같이 사용할 수 있고 추가적으로 catch 아래 finally 라는 구문을 적을 수도 있는데 역할은 try 스코프 안에서 에러가 발생했을 때 catch 로 에러가 잡히는데요. 이 때 끝나는 것이 아니라 무조건 finally 를 에러가 있든 없든 꼭 실행해 준다 라는 의미로 생각하시면 될 것 같습니다.

이상 NodeJS 가 무엇인지와 기본적인 모듈에 대해 설명을 해봤는데요. 앞으로 재미난 기능들이나 공유하고자 하는 지식이 있다면 계속 블로그를 통해 올리도록 하겠습니다. 긴 글 읽어 주셔서 감사합니다!

끄읏~

  • 저는 블록체인 개발사 (주)34일에서 블록체인 엔지니어로 일하고 있습니다.
  • 890만 팔로워 전세계 1위 한류 미디어 케이스타라이브(KStarLive)와 함께 만든 한류 플랫폼에서 사용되는 케이스타코인(KStarCoin) 프로젝트를 진행 중입니다. 팬 커뮤니티 활동을 하면서 코인을 얻을 수 있으며, 한류 콘텐츠 구매, 공연 예매, 한국 관광 상품 구매, 기부 및 팬클럽 활동 등에 사용 될 계획입니다.

--

--