Skip to content

Node.js

Node.js는 V8 (자바스크립트 엔진) 위에서 동작하는 이벤트 처리 I/O 프레임워크이다. 웹 서버와 같이 확장성 있는 네트워크 프로그램 제작을 위해 고안되었다.

Node.js는 파이썬으로 만든 Twisted, 펄로 만든 펄 객체 환경, 루비로 만든 이벤트머신과 그 용도가 비슷하다. 대부분의 자바스크립트가 웹 브라우저에서 실행되는 것과는 달리, Node.js는 서버 측에서 실행된다. Node.js는 일부 CommonJS 명세를 구현하고 있으며, 쌍방향 테스트를 위해 REPL 환경을 포함하고 있다.

Categories

How to install

Ubuntu

Alternatively, for Node.js v4:

curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash -
sudo apt-get install -y nodejs

Alternatively, for Node.js v6:

curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
sudo apt-get install -y nodejs

Optional: install build tools

## To compile and install native addons from npm you may also need to install build tools:
sudo apt-get install -y build-essential

CentOS EPEL

sudo yum install epel-release
sudo yum install nodejs
sudo yum install npm

Arch Linux

sudo pacman -S nodejs npm

Bootstrap script

#!/usr/bin/env bash

ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" || exit; pwd)

N_PREFIX="$ROOT_DIR/.node"
N_DIR="$ROOT_DIR/.n"
N_EXE="$N_DIR/n"
NODE_EXE="$N_PREFIX/bin/node"
NODE_VERSION=18.12.0

if [[ ! -d "$N_DIR" ]]; then
    mkdir -vp "$N_DIR"
fi
if [[ ! -x "$N_EXE" ]]; then
    curl -L "https://raw.githubusercontent.com/tj/n/master/bin/n" -o "$N_EXE"
    chmod +x "$N_EXE"
fi
if [[ ! -x "$N_EXE" ]]; then
    echo "Could not find executable n command on path '$N_EXE'"
    exit 1
fi

if [[ ! -x "$NODE_EXE" ]]; then
    echo "Could not find executable node command on path '$NODE_EXE'"
    echo "Install node $NODE_VERSION ..."

    N_PREFIX="$N_PREFIX" "$N_EXE" install "$NODE_VERSION"

    CODE=$?
    if [[ $CODE -ne 0 ]]; then
        echo "Node $NODE_VERSION installation failed: $CODE"
        exit $CODE
    fi

    echo "Install yarn ..."
    N_PREFIX="$N_PREFIX" "$N_EXE" exec "$NODE_VERSION" npm install --global "yarn"
fi

# Read `.env` variables
if [[ -f "$ROOT_DIR/.env" ]]; then
    while read line; do
        if [[ $(echo "$line" | grep -E '^[ \t]*export[ \t]*[_a-zA-Z0-9]+=.*$') ]]; then
            eval "$line"
        else
            eval "export $line"
        fi
    done < "$ROOT_DIR/.env"
fi

export PATH="$N_PREFIX/bin:$PATH"
N_PREFIX="$N_PREFIX" "$N_EXE" exec "$NODE_VERSION" yarn "$@"

Example

Node.js로 작성한 hello world HTTP 서버:

var http = require('http');

http.createServer(function (request, response) {
    response.writeHead(200, {'Content-Type': 'text/plain'});
    response.end('Hello World\n');
}).listen(8000);

console.log('Server running at http://localhost:8000/');

다른 예제, 7000번 포트를 여는 간단한 TCP Echo 서버:

var net = require('net');

net.createServer(function (stream) {
    stream.write('hello\r\n');

    stream.on('end', function () {
        stream.end('goodbye\r\n');
    });

    stream.pipe(stream);
}).listen(7000);

GET / POST Parameter In Node.js

var sys = require ('sys'),
url = require('url'),
http = require('http'),
qs = require('querystring');

http.createServer(function (req, res) {
    if(req.method=='POST') {
        var body='';
        req.on('data', function (data) {
            body +=data;
        });
        req.on('end',function(){
            var POST =  qs.parse(body);
            console.log(POST);
        });
    } else if(req.method=='GET') {
        var url_parts = url.parse(req.url,true);
        console.log(url_parts.query);
    }
}).listen(1337, "127.0.0.1");

시스템 명령어 전송방법:

var exec = require('child_process').exec;
var cmd = 'prince -v builds/pdf/book.html -o builds/pdf/book.pdf';

exec(cmd, function(error, stdout, stderr) {
  // command output is in stdout
});

Obtain envronment variables

process.env.ENV_VARIABLE

Executable path

실행 파일 경로를 확인하는 방법:

  • process.execPath should be what you require.
  • process.argv0 is not alway pointed to node binary.

명령행으로 HTTP 접속 테스트

보기 편한 코드:

const http = require('http');

http.get('http://localhost:8080', res => {
  if (res.statusCode === 200) {
    process.exit(0);
  } else {
    console.error(`status error: ${res.statusCode}`);
    process.exit(1);
  }
});

bash 스크립트 한줄 코드:

node -e 'require("http").get("http://localhost:8080", res => res.statusCode === 200 ? process.exit(0) : process.exit(1));'

Filesystem

__filename
현재 파일 경로 확인
__dirname
현재 디렉터리 경로 확인 방법

File Logging

__defineGetter__ 사용하기

MDN에 따르면 __defineGetter__는 표준도 아니고 폐기된 API이지만 현재 Node.js에서 사용할 수 있다.(즉, 아직 V8이 지원한다.) 이 특이한 이름의 __defineGetter__는 객체의 프로퍼티를 함수에 바인딩해서 해당 프로퍼티가 조회될 때 바인딩된 함수가 호출되도록 하는데 stdout, stderr를 가로채는 방법을 검색하다 보면 이 방법이 많이 나온다.

fs = require('fs');

console.log('first log message');
console.error('first error message');

var oldStdout = process.stdout,
    oldStderr = process.stderr,
    newStdout = fs.createWriteStream('log.log', {flags: 'a'}),
    newStderr = fs.createWriteStream('err.log', {flags: 'a'});

// stdout, stderr 가로챔
process.__defineGetter__('stdout', function() {
  return newStdout;
});

process.__defineGetter__('stderr', function() {
  return newStderr;
});

console.log('second log message');
console.error('second error message');

// stdout, stderr 복구
process.__defineGetter__('stdout', function() {
  return oldStdout;
});

process.__defineGetter__('stderr', function() {
  return oldStderr;
});

console.log('third log message');
console.error('third error message');

write 함수 오버라이드하기

__defineGetter__를 이용한 방법도 잘 동작하지만 표준이 아닌 __defineGetter__를 사용하는 부분이 약간 찝찝하다. stdout과 stderr 에는 실제 메시지를 출력하는 write 함수가 있는데 이 함수가 실제로 메시지를 출력하는 함수이므로 이 함수를 통째로 가로챌 수 있다.

var fs = require('fs');

console.log('first log message');
console.error('first error message');

var oldStdoutWrite = process.stdout.write,
    oldStderrWrite = process.stderr.write;

// stdout, stderr 가로챔
process.stdout.write = function(str, encoding, fg) {
  fs.appendFile('log.log', str, function(err) {});
}
process.stderr.write = function(str, encoding, fg) {
  fs.appendFile('err.log', str, function(err) {});
}

console.log('second log message');
console.error('second error message');

// stdout, stderr 복구
process.stdout.write = oldStdoutWrite;
process.stderr.write = oldStderrWrite;

console.log('third log message');
console.error('third error message');

TDD

Server And Client App Test

When testing with Node.js, you’ll typically use the following:

Debugging

Corepack

Node.js 기술운영위원회(TSC)가 Corepack을 더 이상 Node.js에 포함해 배포하지 않기로 공식 투표로 결정함. Node.js 25버전부터 적용되며, Node.js 24 이하에서는 실험적 기능으로 계속 제공됨.

Corepack의 역할과 한계

  • Corepack은 Node.js 16.9.0에서 도입된 실험적 도구로, Yarn, pnpm 같은 패키지 매니저를 별도 설치 없이 사용할 수 있게 해줌
  • 프로젝트에서 특정 패키지 매니저 버전을 강제할 수 있도록 도와주는 역할을 수행함
  • 하지만 사용자 인식 부족과 목적에 대한 오해로 인해 널리 사용되지 않음
  • 런타임과 독립적으로 동작해야 할 패키지 매니저를 Node.js에 포함시키는 것에 대한 회의론 존재

단점

ENOSPC: System limit for number of file watchers reached

[Browsersync] Proxying: http://localhost
[Browsersync] Access URLs:
 -------------------------------------
       Local: http://localhost:3000
    External: http://192.168.1.16:3000
 -------------------------------------
          UI: http://localhost:3001
 UI External: http://localhost:3001
 -------------------------------------
[Browsersync] Watching files...
node:internal/errors:464
    ErrorCaptureStackTrace(err);
    ^

Error: ENOSPC: System limit for number of file watchers reached, watch '/my-project-folder/vendor/myclabs/deep-copy/.github'
    at FSWatcher.<computed> (node:internal/fs/watchers:244:19)
    at Object.watch (node:fs:2247:34)
    at createFsWatchInstance (/my-project-folder/node_modules/chokidar/lib/nodefs-handler.js:119:15)
    at setFsWatchListener (/my-project-folder/node_modules/chokidar/lib/nodefs-handler.js:166:15)
    at NodeFsHandler._watchWithNodeFs (/my-project-folder/node_modules/chokidar/lib/nodefs-handler.js:331:14)
    at NodeFsHandler._handleDir (/my-project-folder/node_modules/chokidar/lib/nodefs-handler.js:559:19)
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async NodeFsHandler._addToNodeFs (/my-project-folder/node_modules/chokidar/lib/nodefs-handler.js:609:16)
Emitted 'error' event on FSWatcher instance at:
    at FSWatcher._handleError (/my-project-folder/node_modules/chokidar/index.js:647:10)
    at NodeFsHandler._addToNodeFs (/my-project-folder/node_modules/chokidar/lib/nodefs-handler.js:637:18)
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (node:internal/process/task_queues:96:5) {
  errno: -28,
  syscall: 'watch',
  code: 'ENOSPC',
  path: '/my-project-folder/vendor/myclabs/deep-copy/.github',
  filename: '/my-project-folder/vendor/myclabs/deep-copy/.github'
}

... 와 비슷한 에러를 내뱉는 경우를 볼 수 있다.

원인은 시스템의 파일 와쳐(Watcher)가 limit 에 달해서 그렇다고 한다.

단순한 해결책

echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p

참고로 max_user_watches를 늘리는 게 능사는 아니다. 감시 파일 개수를 줄일 수도 있다.

감시 대상을 제한

node_modules같은 디렉토리는 감시 대상에서 제거하라.

mix.browserSync({
   proxy: 'ws-dev.localhost',
   files: [
      'wp-content/themes/ws/**/*.php',
      'wp-content/themes/ws/build/*.js',
      'wp-content/themes/ws/js/*.js',
      'wp-content/themes/ws/build/*.css',
   ]
});

메모리 사용량, 프로세서 확인

find /proc/*/fd -lname anon_inode:inotify | cut -d/ -f3 | xargs -I '{}' -- ps --no-headers -o '%p %U %c' -p '{}' | uniq -c | sort -nr

Troubleshooting

ENOSPC: System limit for number of file watchers reached

#ENOSPC: System limit for number of file watchers reached 항목 참조.

caniuse-lite is outdated

경고 메시지 출력:

rowserslist: caniuse-lite is outdated. Please run:
npx browserslist@latest --update-db

나온 대로 그냥 돌려주자:

npx browserslist@latest --update-db

See also

  • Event driven architecture
  • left-pad (NPM, left-pad 사건)
  • deno
  • Bun - JavaScriptCore 기반의 빠른 JavaScript 런타임/트랜스파일러/NPM 클라이언트
  • Bare - 빠르고 경량화된 모듈형 JavaScript 런타임
  • php-node - Node.js내에서 PHP HTTP 요청을 처리하는 핸들러
  • qnm - node_modules 폴더를 들여다 보는 CLI 도구

Favorite site

Article

Tutorial

Guide

Packages

Deploy

Stack

References


  1. Seokjun.kim_-_node-js-tdd.pdf 

  2. The_Node_Beginner_Book_(Korean_version)_A_comprehensive_Node.pdf 

  3. Felix's_node.js_Beginners_Guide.pdf 

  4. Node_v0.2.6-ko.pdf 

  5. Bcho.tistory.com_-node.js-_Introduction_and_internal_structure.pdf 

  6. Node.js_File_System.pdf 

  7. Dockerizing_a_NodeJS_web_app_-_Node.pdf 

  8. Naver_D2_-Nodejs-_Quickly_develop_services.pdf