모듈과 exports
CommonJS와 ES 모듈 방식으로 모듈을 정의하고 `module.exports`/`export`를 관리하는 패턴을 정리합니다.
// math.cjs
const add = (left, right) => left + right;
module.exports = { add };Category
Preparing references and filters for this topic. 이 주제의 레퍼런스와 필터를 준비하고 있습니다.
CommonJS와 ES 모듈 방식으로 모듈을 정의하고 `module.exports`/`export`를 관리하는 패턴을 정리합니다.
// math.cjs
const add = (left, right) => left + right;
module.exports = { add };`package.json` 카드는 필드 나열보다 `팀이 무엇을 어떤 진입점으로 실행하게 할 것인가`를 정리하는 카드입니다. scripts와 npm 실행 흐름 중심으로 다시 봅니다.
{
"type": "module",
"scripts": {
"dev": "node --watch src/index.js",
"build": "tsc",
"test": "node --test"
}
}Node.js에서 ESM과 CommonJS를 섞어 쓸 때의 기본 규칙, `import()`, `createRequire()` 활용 기준을 정리합니다.
// index.mjs (ESM: .mjs 또는 "type": "module")
import legacy from "./legacy.cjs";
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
const packageJson = require("./package.json");
console.log(legacy, packageJson.name);`package.json`의 `exports`와 `imports` 필드, conditional exports가 패키지 공개 경계를 어떻게 정하는지 정리합니다.
{
"name": "my-lib",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
},
"./utils": {
"types": "./dist/utils.d.ts",
"import": "./dist/utils.mjs",
"require": "./dist/utils.cjs"
}
},
"imports": {
"#internal/*": "./src/internal/*.js"
}
}ESM에서 `import.meta`, file URL, `__dirname` 대체 패턴을 어떻게 다뤄야 하는지 정리합니다.
import { readFile } from "node:fs/promises";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
const configUrl = new URL("./config/default.json", import.meta.url);
const text = await readFile(configUrl, "utf8"); // fs는 file: URL을 받음
const currentFile = fileURLToPath(import.meta.url); // path API가 필요할 때만 변환
const configPath = join(dirname(currentFile), "config", "default.json");
console.log(text, configPath);Node.js 패키지를 재사용 가능한 라이브러리나 CLI로 배포할 때 `package.json`의 핵심 필드와 `bin` 구성을 어떻게 읽는지 정리합니다.
{
"name": "my-tool",
"version": "1.0.0",
"type": "module",
"bin": {
"my-tool": "./bin/cli.js"
},
"files": ["bin", "dist", "README.md"],
"exports": {
".": "./dist/index.js"
}
}이벤트 루프 카드는 phase 이름 암기보다 `왜 이 콜백이 먼저 실행됐는가`를 설명할 수 있어야 합니다. microtask, timer, I/O 이후 순서를 다시 찾을 때 보는 기준 카드입니다.
console.log("A");
setTimeout(() => console.log("timeout"), 0);
Promise.resolve().then(() => console.log("promise"));
queueMicrotask(() => console.log("microtask"));
setImmediate(() => console.log("immediate"));
console.log("B");Node 타이머 카드는 API 이름보다 `반복 작업을 어떻게 안전하게 예약할까`를 먼저 답해야 합니다. `setTimeout`, `setInterval`, `setImmediate`, `queueMicrotask` 경계를 정리합니다.
let stopped = false;
let nextTimer;
async function refreshStatus() {
const response = await fetch("https://api.example.com/status");
if (!response.ok) throw new Error(`HTTP ${response.status}`);
}
function schedulePoll() {
nextTimer = setTimeout(async () => {
try {
await refreshStatus();
} catch (error) {
console.error("poll failed:", error);
} finally {
if (!stopped) schedulePoll();
}
}, 1000);
}
schedulePoll();
function stopPolling() {
stopped = true;
clearTimeout(nextTimer);
}`EventEmitter` 카드는 API 사용법보다 `이 이벤트가 동기인가`, `누가 cleanup 책임을 지는가`, `error 이벤트를 누가 받는가`를 정리하는 카드입니다.
import { EventEmitter } from "node:events";
const jobs = new EventEmitter();
function onProgress({ id }) {
console.log("progress:", id);
}
jobs.on("error", (error) => console.error("job failed:", error));
jobs.once("ready", () => console.log("initialized once"));
jobs.on("progress", onProgress);
jobs.emit("ready");
jobs.emit("progress", { id: "job-1" }); // listener를 지금 호출한다
jobs.off("progress", onProgress);취소와 실패를 같은 에러로 다루면 흐름이 흐려집니다. `AbortController`, `AbortSignal.timeout`, `AbortSignal.any`를 `취소는 정상 흐름`이라는 관점에서 정리합니다.
async function loadStatus(shutdownSignal) {
const signal = AbortSignal.any([
AbortSignal.timeout(3000),
shutdownSignal,
]);
try {
return await fetch("https://api.example.com/status", { signal });
} catch (error) {
if (signal.aborted) {
console.warn("request canceled:", signal.reason);
return null;
}
throw error;
}
}
const shutdown = new AbortController();
process.once("SIGTERM", () => shutdown.abort(new Error("server shutdown")));
await loadStatus(shutdown.signal);Node.js `crypto` 모듈에서 해시와 난수 생성이 각각 어떤 용도에 맞는지 정리합니다.
import { createHash, createHmac, randomBytes, randomUUID } from "node:crypto";
const fileHash = createHash("sha256").update(Buffer.from("file content")).digest("hex");
const apiToken = randomBytes(32).toString("hex");
const requestId = randomUUID();
const signature = createHmac("sha256", "test-secret").update("payload").digest("hex");`worker_threads` 카드는 멀티스레드 소개 카드가 아니라 `이 작업이 진짜 CPU 바운드인가`를 판단하는 카드입니다. 워커를 써야 하는 경우와 아닌 경우를 먼저 정리합니다.
import { Worker } from "node:worker_threads";
function runSum(values) {
return new Promise((resolve, reject) => {
const worker = new Worker(new URL("./sum-worker.mjs", import.meta.url), {
workerData: { values },
});
worker.once("message", resolve);
worker.once("error", reject);
worker.once("exit", (code) => {
if (code !== 0) reject(new Error(`worker exited with ${code}`));
});
});
}
console.log(await runSum([10, 20, 30]));`worker_threads`를 실전에 붙일 때의 pool 전략, `MessageChannel`, transfer, shared memory 기준을 정리합니다.
import { Worker } from "node:worker_threads";
const createWorker = () => new Worker(new URL("./compute-worker.mjs", import.meta.url));
const idle = [createWorker(), createWorker()];
const waiting = [];
let restartCount = 0;
let poolError;
function dispatch(payload) {
if (poolError) return Promise.reject(poolError);
return new Promise((resolve, reject) => {
waiting.push({ payload, resolve, reject });
drain();
});
}
function drain() {
const worker = idle.pop();
const job = waiting.shift();
if (!worker || !job) {
if (worker) idle.push(worker);
if (job) waiting.unshift(job);
return;
}
const cleanup = () => {
worker.off("message", onMessage);
worker.off("error", onError);
worker.off("exit", onExit);
};
const replaceWorker = (error) => {
if (restartCount >= 5) {
poolError = new Error("worker pool restart limit reached", { cause: error });
while (waiting.length) waiting.shift().reject(poolError);
return;
}
restartCount += 1;
idle.push(createWorker());
drain();
};
const onMessage = (result) => {
cleanup();
restartCount = 0;
idle.push(worker);
job.resolve(result);
drain();
};
const onError = (error) => {
cleanup();
job.reject(error);
replaceWorker(error);
};
const onExit = (code) => {
const error = new Error(`worker exited: ${code}`);
cleanup();
job.reject(error);
replaceWorker(error);
};
worker.once("message", onMessage);
worker.once("error", onError);
worker.once("exit", onExit);
try {
worker.postMessage(job.payload);
} catch (error) {
onError(error);
}
}Node.js의 `diagnostics_channel`을 이용해 라이브러리와 애플리케이션 사이 관측성 신호를 느슨하게 연결하는 패턴을 정리합니다.
import diagnostics_channel from "node:diagnostics_channel";
const channel = diagnostics_channel.channel("myapp:http:request");
function publishRequest(message) {
if (channel.hasSubscribers) channel.publish(message);
}
diagnostics_channel.subscribe("myapp:http:request", (message) => {
console.log("[trace]", message.method, message.url, message.durationMs);
});Node의 async 에러 카드는 문법보다 `에러가 어디서 사라졌는가`를 추적하는 데 써야 합니다. await 누락, 체인 중간 catch, 최상위 rejection 처리 기준을 한 장에 모았습니다.
async function main() {
const data = await Promise.resolve({ status: "ok" });
console.log(data);
}
void main().catch((err) => {
console.error("main failed:", err);
process.exitCode = 1;
});Node.js에서 요청 ID, 사용자 ID, trace 정보를 비동기 호출 체인 전체에 전달할 때 AsyncLocalStorage를 어떻게 쓰는지 정리합니다.
import { AsyncLocalStorage } from "node:async_hooks";
import { createServer } from "node:http";
import { randomUUID } from "node:crypto";
const requestContext = new AsyncLocalStorage();
function log(message) {
const store = requestContext.getStore();
console.log(store?.requestId ?? "-", message);
}
const server = createServer((req, res) => {
void requestContext.run({ requestId: randomUUID() }, async () => {
try {
log(`${req.method} ${req.url}`);
await Promise.resolve(); // service, database, fetch 등 비동기 경계
log("response sent");
res.end("ok");
} catch (error) {
log(error.message);
res.statusCode = 500;
res.end("internal error");
}
});
});
server.listen(3000);Node.js의 perf_hooks로 함수, I/O 구간, 비동기 작업 시간을 측정할 때 performance mark, measure, observer를 어떻게 쓰는지 정리합니다.
import { performance, PerformanceObserver } from "node:perf_hooks";
const observer = new PerformanceObserver((items) => {
for (const entry of items.getEntries()) {
console.log(entry.name, entry.duration);
}
});
observer.observe({ entryTypes: ["measure"] });
async function measure(name, task) {
const start = `${name}:start`;
const end = `${name}:end`;
performance.mark(start);
try {
return await task();
} finally {
performance.mark(end);
performance.measure(name, start, end);
performance.clearMarks(start);
performance.clearMarks(end);
}
}
const config = await measure("load config", async () => ({ port: 3000 }));
console.log(config);`createServer`로 기본 요청/응답 흐름을 만들고, 라우팅 없이 바로 응답하는 간단한 HTTP 서버 패턴을 보여줍니다.
import { createServer } from "node:http";
function sendJson(res, status, body) {
res.writeHead(status, { "content-type": "application/json; charset=utf-8" });
res.end(JSON.stringify(body));
}
const server = createServer((req, res) => {
const url = new URL(req.url ?? "/", "http://localhost");
if (req.method === "GET" && url.pathname === "/status") return sendJson(res, 200, { ok: true });
return sendJson(res, 404, { error: "not found" });
});
server.listen(3000, () => console.log("http://localhost:3000"));Node.js 18+의 내장 `fetch`로 외부 API를 호출하고 JSON 응답을 읽는 기본 패턴입니다.
async function fetchJson(url, { signal = AbortSignal.timeout(5000) } = {}) {
const response = await fetch(url, {
signal,
headers: { accept: "application/json" },
});
if (!response.ok) {
const body = await response.text();
throw new Error(`HTTP ${response.status}: ${body.slice(0, 200)}`);
}
const contentType = response.headers.get("content-type") ?? "";
if (!/^application\/(?:[a-z0-9.+-]+\+)?json\b/i.test(contentType)) {
throw new Error(`expected JSON, received ${contentType || "unknown content type"}`);
}
return response.json();
}
const user = await fetchJson("https://api.example.com/users/1");
console.log(user.name);Node.js 기본 HTTP 서버에서 요청 본문을 읽고 JSON 응답을 보내는 가장 실전적인 패턴을 정리합니다.
import { createServer } from "node:http";
const MAX_BODY_BYTES = 1_048_576;
const sendJson = (res, status, body) => {
res.writeHead(status, { "content-type": "application/json; charset=utf-8" });
res.end(JSON.stringify(body));
};
async function readJson(req) {
const chunks = [];
let size = 0;
for await (const chunk of req) {
size += chunk.length;
if (size > MAX_BODY_BYTES) throw Object.assign(new Error("payload too large"), { status: 413 });
chunks.push(chunk);
}
try {
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
} catch {
throw Object.assign(new Error("invalid JSON"), { status: 400 });
}
}
createServer(async (req, res) => {
if (req.method !== "POST" || req.url !== "/users") return sendJson(res, 404, { error: "not found" });
if (!req.headers["content-type"]?.startsWith("application/json")) return sendJson(res, 415, { error: "JSON required" });
try {
const payload = await readJson(req);
return sendJson(res, 201, { ok: true, name: payload.name });
} catch (error) {
return sendJson(res, error.status ?? 500, { error: error.message });
}
}).listen(3000);현대 Node.js가 제공하는 `fetch`, `Request`, `Response`, `FormData`, `Headers` 같은 Web API 표면을 정리합니다.
import { readFile } from "node:fs/promises";
// 파일을 Blob으로 만들어 FormData에 첨부
const fileBuffer = await readFile("./photo.jpg");
const blob = new Blob([fileBuffer], { type: "image/jpeg" });
const form = new FormData();
form.append("name", "kero");
form.append("avatar", blob, "photo.jpg"); // 파일명도 함께 전달
// Content-Type은 직접 설정하지 않는다 — boundary가 자동 생성됨
const response = await fetch("https://api.example.com/users", {
method: "POST",
body: form,
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const result = await response.json();HTTP 서버가 단순히 요청을 받는 것을 넘어, timeout과 keep-alive를 어떻게 다루고 종료 시 어떤 순서로 정리해야 하는지 정리합니다.
import { createServer } from "node:http";
const server = createServer((req, res) => res.end("ok"));
server.requestTimeout = 30_000; // 요청 전체 수신 제한
server.headersTimeout = 10_000; // 헤더 전체 수신 제한
server.keepAliveTimeout = 5_000; // 응답 뒤 유휴 연결 대기
server.listen(3000);
let shuttingDown = false;
process.on("SIGTERM", () => {
if (shuttingDown) return;
shuttingDown = true;
let finished = false;
const forceClose = setTimeout(() => {
if (!finished) server.closeAllConnections();
}, 10_000);
forceClose.unref();
server.close((error) => {
finished = true;
clearTimeout(forceClose);
process.exitCode = error ? 1 : 0;
});
server.closeIdleConnections(); // Node 18.2+; Node 19+에서는 close()가 처리
});Node.js HTTP client에서 Agent가 socket 재사용과 연결 수를 어떻게 관리하는지, keepAlive와 maxSockets 설정 기준을 정리합니다.
import { Agent, request } from "node:https";
const agent = new Agent({
keepAlive: true,
maxSockets: 50,
maxFreeSockets: 10,
timeout: 30_000,
});
const req = request("https://api.example.com/users", { agent }, (res) => {
res.resume();
});
req.end();Node.js에서 운영체제 이름 해석을 따르는 dns.lookup과 DNS 질의를 직접 수행하는 dns.resolve 계열의 차이를 정리합니다.
import { lookup } from "node:dns/promises";
import { resolve4, resolveMx } from "node:dns/promises";
const address = await lookup("example.com");
const aRecords = await resolve4("example.com");
const mxRecords = await resolveMx("example.com");`process.env`와 `argv` 카드는 값 읽기 자체보다 `입력 경계`를 설계하는 카드입니다. 환경 변수 타입 변환과 CLI 인자 해석을 한 장에서 다시 정리합니다.
const args = process.argv.slice(2);
function option(name) {
const value = args.find((arg) => arg.startsWith(`${name}=`));
return value?.slice(name.length + 1);
}
const portText = option("--port") ?? process.env.PORT ?? "3000";
const port = Number(portText);
if (!Number.isInteger(port) || port < 1 || port > 65535) {
throw new Error("port must be an integer between 1 and 65535");
}
export const config = {
port,
debug: process.env.DEBUG === "true",
file: option("--file"),
};터미널 입력을 받아 간단한 CLI를 만드는 Node.js `readline` 기본 흐름을 정리합니다.
import readline from "node:readline/promises";
import { stdin as input, stdout as output } from "node:process";
const rl = readline.createInterface({ input, output });
const controller = new AbortController();
rl.once("SIGINT", () => controller.abort());
try {
const name = await rl.question("이름을 입력하세요: ", {
signal: controller.signal,
});
console.log(`입력: ${name}`);
} catch (error) {
if (controller.signal.aborted) process.exitCode = 130;
else throw error;
} finally {
rl.close();
}외부 프로그램을 실행할 때 자주 비교하는 `spawn()`과 `exec()`의 차이를 정리합니다.
import { execFile, spawn } from "node:child_process";
const child = spawn("git", ["log", "--oneline", "-20"]);
child.stdout.on("data", (chunk) => process.stdout.write(chunk));
child.stderr.on("data", (chunk) => process.stderr.write(chunk));
child.on("error", (error) => console.error("failed to start:", error));
child.on("close", (code, signal) => {
if (code !== 0) console.error({ code, signal });
});
execFile("git", ["status", "--short"], (error, stdout) => {
if (error) {
console.error(error);
return;
}
console.log(stdout);
});종료 카드는 `exit(1)` 문법보다 `무엇을 먼저 정리하고 언제 종료할 것인가`를 정하는 카드입니다. SIGTERM, SIGINT, graceful shutdown 기준을 중심으로 정리합니다.
import { createServer } from "node:http";
const server = createServer((req, res) => res.end("ok"));
server.listen(3000);
let shuttingDown = false;
function closeServer() {
return new Promise((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()));
server.closeIdleConnections();
});
}
async function shutdown(signal) {
if (shuttingDown) return;
shuttingDown = true;
console.log(`received ${signal}; draining connections`);
const forceClose = setTimeout(() => server.closeAllConnections(), 10_000);
forceClose.unref();
try {
await closeServer();
process.exitCode = 0;
} catch (error) {
console.error(error);
process.exitCode = 1;
} finally {
clearTimeout(forceClose);
}
}
process.on("SIGTERM", () => void shutdown("SIGTERM"));
process.on("SIGINT", () => void shutdown("SIGINT"));내장 테스트 러너 `node:test`로 작은 단위 테스트를 구성하는 기본 흐름을 정리합니다.
import test from "node:test";
import assert from "node:assert/strict";
function formatPrice(value) {
return `${value.toLocaleString("ko-KR")}원`;
}
test("정수 금액을 천 단위로 포맷한다", () => {
assert.strictEqual(formatPrice(1000), "1,000원");
});
test("비동기 함수도 검증한다", async () => {
const result = await Promise.resolve({ id: 1, name: "Alice" });
assert.deepStrictEqual(result, { id: 1, name: "Alice" });
});실행 환경 정보를 얻는 `os` 모듈의 핵심 함수와 적절한 사용 범위를 정리합니다.
import os from "node:os";
const workerCount = Math.max(1, os.availableParallelism() - 1);
const totalMem = os.totalmem();
const freeMem = os.freemem();
console.log({
workerCount,
platform: os.platform(),
arch: os.arch(),
homedir: os.homedir(),
tmpdir: os.tmpdir(),
memoryUsagePercent: ((1 - freeMem / totalMem) * 100).toFixed(1),
});환경 변수와 env 파일을 어디까지 설정값으로 쓰고, 어떤 값은 별도 config 검증으로 올려야 하는지 정리합니다.
// Node.js 20.6+ — dotenv 없이 .env 직접 로드
// node --env-file=.env server.js
function required(name) {
const value = process.env[name];
if (!value) throw new Error(`${name} is required`);
return value;
}
function parsePort(value = "3000") {
const port = Number(value);
if (!Number.isInteger(port) || port < 1 || port > 65535) {
throw new Error("PORT must be an integer between 1 and 65535");
}
return port;
}
const port = parsePort(process.env.PORT);
const debug = process.env.DEBUG === "true";
const dbUrl = required("DATABASE_URL");
export const config = { port, debug, dbUrl };Node.js cluster 모듈로 CPU 코어를 활용하는 방법, worker_threads와의 선택 기준, 프로세스 장애 복구 패턴을 정리합니다.
import cluster from "node:cluster";
import http from "node:http";
import { availableParallelism } from "node:os";
if (cluster.isPrimary) {
const restartTimes = [];
const canRestart = () => {
const now = Date.now();
while (restartTimes[0] < now - 60_000) restartTimes.shift();
if (restartTimes.length >= 5) return false;
restartTimes.push(now);
return true;
};
for (let i = 0; i < availableParallelism(); i++) {
cluster.fork();
}
cluster.on("exit", (worker, code, signal) => {
console.error(`worker ${worker.process.pid} exited`, { code, signal });
if (!worker.exitedAfterDisconnect && canRestart()) cluster.fork();
});
} else {
http.createServer((req, res) => {
res.end(`worker ${process.pid}`);
}).listen(3000);
}Node.js 20+에서 도입되고 22.13+/23.5+에서 안정화된 Permission Model로 파일·네트워크·자식 프로세스 접근을 제한하는 방법, 보안 경계 설정 전략을 정리합니다.
node \
--permission \
--allow-fs-read="/app/config" \
--allow-fs-write="/app/tmp" \
--allow-net \
server.js비동기 파일 I/O 카드는 API 목록보다 `readFile로 끝낼까, stream으로 넘길까, Promise.all로 묶을까`를 판단하는 카드입니다. `fs/promises` 기준으로 다시 정리합니다.
import { mkdir, readFile, writeFile } from "node:fs/promises";
await mkdir("./logs", { recursive: true });
await writeFile("./logs/app.log", "started\n", "utf8");
const [config, labels] = await Promise.all([
readFile("./config.json", "utf8"),
readFile("./labels.json", "utf8"),
]);
console.log(config.length, labels.length);Node에서 경로 문제는 `join vs resolve`, `경로 vs file URL`, `ESM의 import.meta.url` 세 갈래로 자주 다시 나옵니다. 이 셋을 한 장에서 빠르게 정리합니다.
import path from "node:path";
import { readFile } from "node:fs/promises";
import { fileURLToPath } from "node:url";
const relative = path.join("src", "utils", "helper.js");
const absolute = path.resolve("src", "utils", "helper.js");
const fileUrl = new URL("./data.json", import.meta.url);
const text = await readFile(fileUrl, "utf8");
const filePath = fileURLToPath(fileUrl); // 문자열 path가 필요한 API에서만
console.log(relative, absolute, filePath, text);스트림 카드는 API 이름보다 `언제 readFile 대신 stream을 써야 하는가`를 먼저 답해야 합니다. Readable/Writable/Transform과 backpressure 감각을 한 장에 모았습니다.
import { createReadStream, createWriteStream } from "node:fs";
import { pipeline } from "node:stream/promises";
import { createGzip } from "node:zlib";
await pipeline(
createReadStream("./big.log"),
createGzip(),
createWriteStream("./big.log.gz"),
);문자열이 아니라 바이트 단위 데이터를 다루는 Node.js `Buffer`의 기본 개념과 사용 흐름을 정리합니다.
const buf = Buffer.from("hello world", "utf8");
console.log(buf.length); // 11 bytes
console.log(buf.toString("hex")); // 68656c6c6f20776f726c64
console.log(buf.toString("base64")); // aGVsbG8gd29ybGQ=
const fixed = Buffer.alloc(8);
fixed.writeUInt32BE(0xdeadbeef, 0);
console.log(fixed.toString("hex")); // deadbeef00000000여러 스트림을 안전하게 연결하고 완료 여부를 확인할 때 쓰는 `pipeline()`과 `finished()` 패턴을 정리합니다.
import { createReadStream, createWriteStream } from "node:fs";
import { pipeline } from "node:stream/promises";
import { createGzip } from "node:zlib";
await pipeline(
createReadStream("./input.log"),
createGzip(),
createWriteStream("./output.log.gz"),
);파일 변경 감지용 `fs.watch()`와 개발 중 재실행용 `node --watch`를 어떻게 구분해 써야 하는지 정리합니다.
import { watch } from "node:fs";
const controller = new AbortController();
let timer;
watch("./src", { recursive: true, signal: controller.signal }, (eventType, filename) => {
clearTimeout(timer);
timer = setTimeout(() => {
console.log(`rescan after ${eventType}: ${filename ?? "unknown"}`);
}, 100);
});
process.once("SIGINT", () => controller.abort());Node.js `zlib`로 gzip/deflate/brotli 압축을 적용할 때 알고리즘 선택 기준, 스트림 기반 사용, HTTP 응답 압축 패턴을 정리합니다.
import { createReadStream, createWriteStream } from "node:fs";
import { pipeline } from "node:stream/promises";
import { createGzip } from "node:zlib";
await pipeline(
createReadStream("./app.log"),
createGzip(),
createWriteStream("./app.log.gz"),
);현대 Node.js에서 공존하는 Web Streams와 전통적인 Node streams가 무엇이 다르고, 어느 경계에서 만나는지 정리합니다.
import { createWriteStream } from "node:fs";
import { Readable } from "node:stream";
import { pipeline } from "node:stream/promises";
const response = await fetch("https://example.com/large-file.bin");
if (!response.ok || !response.body) throw new Error(`download failed: ${response.status}`);
await pipeline(
Readable.fromWeb(response.body),
createWriteStream("./output.bin"),
);Node.js 22.5+에서 추가된 node:sqlite 모듈로 외부 패키지 없이 SQLite를 사용하는 방법, 동기 API 특성과 적합한 사용 시나리오를 정리합니다.
import { DatabaseSync } from "node:sqlite";
const db = new DatabaseSync("./data.db", { timeout: 5000 });
try {
db.exec("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)");
db.prepare("INSERT INTO users (name) VALUES (?)").run("kero");
console.log(db.prepare("SELECT * FROM users").all());
} finally {
db.close();
}