핵심: 예전엔 npm 패키지로만 가능하던 기능들이 이제는 Node.js 코어에 내장되어 있다.
결과: 의존성 줄고, 보안·유지보수·빌드 속도 모두 개선 가능.
이 글은 원문에서 정리한 “기존에 인기 있던 npm 패키지 ↔ 이를 대체하는 최신 Node 기능”을 하나씩 정리한 것이다.
정리 기준:
Node 생태계는 오랫동안 아래 같은 패턴으로 굴러왔다.
하지만 최근 몇 년간:
→ 자연스럽게 “굳이 패키지를 쓸 필요가 없는 영역”이 계속 넓어지고 있다.
이제는 “무조건 npm부터 검색”이 아니라,
- Node 코어에서 이미 지원하는지 확인
- 굳이 외부 패키지를 쓸 이유가 있는지 검토
가 더 적절한 순서가 된다.
node-fetch → 글로벌 fetch()fetch()를 쓰려면 node-fetch 같은 패키지를 설치해야 했다.fetch() 제공.const res = await fetch('https://api.github.com/repos/nodejs/node');
const data = await res.json();
console.log(data.full_name); // "nodejs/node"
node-fetch를 써야 하나?fetch()로 일원화하는 편이 이득.ws(클라이언트) → 글로벌 WebSocket (클라이언트용)ws 패키지가 사실상 표준.WebSocket(클라이언트) 지원(실험적).const ws = new WebSocket('wss://echo.websocket.org');
ws.onopen = () => ws.send('Hello!');
ws.onmessage = (event) => console.log('Received:', event.data);
ws를 계속 써야 하나?ws 또는 그 위에 만들어진 고수준 라이브러리가 사실상 표준.ws 유지.node:testnode:test라는 내장 테스트 러너 제공.import test from 'node:test';
import assert from 'node:assert';
test('addition works', () => {
assert.strictEqual(2 + 2, 4);
});
node:test만으로도 충분한 경우가 많다.sqlite3 / better-sqlite3 → node:sqlite (실험적)sqlite3 (네이티브 바인딩, 컴파일 필요)better-sqlite3(성능·동기 API 강점)업그레이드/플랫폼 변경 시 네이티브 모듈 깨지는 이슈가 흔했다.
node:sqlite 모듈을 도입.import { open } from 'node:sqlite';
const db = await open(':memory:');
await db.exec('CREATE TABLE users (id INTEGER, name TEXT)');
현재로서는 “앞으로 이 방향으로 갈 것이다” 정도로 보고,
실무에서는 여전히 better-sqlite3 등이 주력이다.
chalk / kleur → util.styleText()chalk, kleur 등이 사실상 표준.node:util 모듈에 styleText() 내장.import { styleText } from 'node:util';
console.log(styleText('red', 'Error!'));
console.log(styleText(['bold', 'green'], 'Success!'));
chalk를 쓸 이유가 남는가?chalk.red.bold('msg')) 선호그 외에는 새 프로젝트에서는 기본 styleText부터 고려할 수 있다.
ansi-colors / strip-ansi → util.stripVTControlCharacters()strip-ansi, ansi-colors 등 사용.node:util에서 제공:import { stripVTControlCharacters } from 'node:util';
const text = '\u001B[4mUnderlined\u001B[0m';
console.log(stripVTControlCharacters(text)); // "Underlined"
glob → fs.glob()**/*.js 등)을 위해 glob 패키지 필수.fs.glob() 지원.import fs from 'node:fs/promises';
const files = await fs.glob('**/*.js');
console.log(files);
glob를 계속 써야 하나?rimraf → fs.rm({ recursive: true })rimraf가 거의 필수.fs.rm()에서 재귀 삭제 지원:import fs from 'node:fs/promises';
await fs.rm('dist', { recursive: true, force: true });
fs.rm() + recursive 옵션은 Node 14+에서 안정적으로 사용 가능rimraf 설치할 이유는 거의 없다.fs.rm으로 통일.mkdirp → fs.mkdir({ recursive: true })mkdirp 패키지를 많이 사용.import fs from 'node:fs/promises';
await fs.mkdir('logs/app', { recursive: true });
recursive 옵션 추가mkdirp를 쓸 이유가 거의 없다.uuid(v4) → crypto.randomUUID()uuid 패키지 사용.import { randomUUID } from 'node:crypto';
console.log(randomUUID());
uuid를 쓰나?그 외에는 crypto.randomUUID()가 기본 선택지.
base64-js / atob polyfill → Buffer, atob, btoabase64-jsatob/btoa polyfill 등 사용.atob/btoa + 기존 Buffer 제공.const encoded = btoa('hello');
console.log(encoded); // "aGVsbG8="
console.log(atob(encoded)); // "hello"
atob/btoa 글로벌은 대략 Node 20 이후url-pattern → 글로벌 URLPattern (실험적)url-pattern 같은 패키지 사용.URLPattern API를 글로벌로 제공(실험적).const pattern = new URLPattern({ pathname: '/users/:id' });
const match = pattern.exec('/users/42');
console.log(match.pathname.groups.id); // "42"
url-pattern이 여전히 유효한가?dotenv(기본 기능) → --env-file 플래그 (실험적).env 파일 로딩:dotenv 패키지가 사실상 표준..env 로딩:node --env-file=.env app.js
dotenv를 계속 써야 하나?${VAR}), 여러 .env 파일 병합 등 고급 기능 필요event-target-shim → 글로벌 EventTargetEventEmitter는 있었지만,EventTarget이 필요할 경우 event-target-shim 사용.const target = new EventTarget();
target.addEventListener('ping', () => console.log('pong'));
target.dispatchEvent(new Event('ping'));
tsc(기본 트랜스파일) → Node의 TypeScript 스트립 기능 (실험적).ts 실행:tsc 빌드 or ts-node 같은 런타임 트랜스파일러 필요.node --experimental-strip-types app.ts
tsc가 필요할까?.d.ts) 생성즉, 이 기능은 “간단한 TS 스크립트를 빠르게 돌려볼 때” 정도로 보는 게 적절하고,
실제 프로덕션 빌드는 여전히 tsc 기반이라고 보면 된다.
Node의 진화 방향은 명확하다.
이 변화가 주는 실무적인 이점:
fetch, URLPattern, EventTarget 등 웹 표준 API를 그대로 사용실제 코드베이스에서 이 내용을 적용하려면:
package.json 기준으로 아래 패키지 사용 여부 체크:node:sqlite, URLPattern, --env-file, --experimental-strip-types 등은: