Contents
see ListNode.js 22는 2024년 10월 LTS로 승격된 이후 2025-2026년 서버 사이드 JavaScript의 표준이 되었다. 네이티브 TypeScript 지원, 내장 WebSocket 클라이언트, Permission Model 등 혁신적인 기능들을 실전 코드와 함께 살펴본다.
1. 네이티브 TypeScript 실행 (--experimental-strip-types)
Node.js 22.6부터 TypeScript 파일을 별도 빌드 없이 직접 실행할 수 있다. ts-node나 tsx 같은 서드파티 도구 없이도 .ts 파일을 바로 실행 가능하다.
// server.ts - TypeScript로 작성
import { createServer, IncomingMessage, ServerResponse } from "node:http";
interface User {
id: number;
name: string;
email: string;
}
const users: User[] = [
{ id: 1, name: "김개발", email: "dev@example.com" },
{ id: 2, name: "이코딩", email: "coding@example.com" }
];
const server = createServer((req: IncomingMessage, res: ServerResponse) => {
if (req.url === "/api/users" && req.method === "GET") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(users));
} else {
res.writeHead(404);
res.end("Not Found");
}
});
server.listen(3000, () => {
console.log("서버 실행: http://localhost:3000");
});
# 직접 실행 - 빌드 불필요
node --experimental-strip-types server.ts
# Node.js 23+에서는 플래그 없이 실행 가능
node server.ts
주의사항: 타입 스트리핑만 지원하며, enum이나 namespace 같은 TypeScript 전용 런타임 기능은 사용할 수 없다. 순수 타입 어노테이션만 제거되는 방식이다.
2. 내장 WebSocket 클라이언트
Node.js 22부터 WebSocket 클라이언트가 내장되어 ws 패키지 없이 WebSocket 통신이 가능하다.
// WebSocket 클라이언트 - 외부 패키지 불필요
const ws = new WebSocket("wss://echo.websocket.org");
ws.addEventListener("open", () => {
console.log("연결 성공");
ws.send(JSON.stringify({ type: "ping", timestamp: Date.now() }));
});
ws.addEventListener("message", (event) => {
const data = JSON.parse(event.data);
console.log("수신:", data);
});
ws.addEventListener("close", (event) => {
console.log(`연결 종료: ${event.code} ${event.reason}`);
});
ws.addEventListener("error", (event) => {
console.error("에러 발생:", event);
});
// 실시간 주식 데이터 수신 예시
async function connectStockFeed(symbols: string[]) {
const ws = new WebSocket("wss://stream.example.com/stocks");
ws.addEventListener("open", () => {
ws.send(JSON.stringify({
action: "subscribe",
symbols: symbols
}));
});
ws.addEventListener("message", (event) => {
const tick = JSON.parse(event.data);
console.log(`${tick.symbol}: ${tick.price} (${tick.change > 0 ? "+" : ""}${tick.change}%)`);
});
// 30초마다 핑 전송
const pingInterval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: "ping" }));
}
}, 30000);
ws.addEventListener("close", () => clearInterval(pingInterval));
return ws;
}
connectStockFeed(["AAPL", "GOOGL", "TSLA"]);
3. Permission Model (보안 강화)
Node.js 22의 Permission Model은 파일 시스템, 네트워크, 자식 프로세스 접근을 세밀하게 제어한다.
# 파일 읽기만 허용 (쓰기 금지)
node --experimental-permission --allow-fs-read=./data app.js
# 특정 디렉토리만 읽기/쓰기 허용
node --experimental-permission \
--allow-fs-read=./config,./src \
--allow-fs-write=./logs,./tmp \
app.js
# 네트워크 접근 제한
node --experimental-permission \
--allow-fs-read=./ \
--allow-net=api.example.com:443 \
app.js
# 자식 프로세스 실행 허용
node --experimental-permission \
--allow-child-process \
--allow-fs-read=./ \
worker-manager.js
// 런타임에서 권한 확인
import { permission } from "node:process";
if (permission.has("fs.read", "./config")) {
const config = JSON.parse(
await fs.readFile("./config/app.json", "utf-8")
);
} else {
console.warn("설정 파일 읽기 권한 없음, 기본값 사용");
}
4. node:test 러너 안정화
내장 테스트 러너가 안정화되어 Jest/Mocha 없이도 테스트를 작성할 수 있다.
// test/user.test.ts
import { describe, it, before, after, mock } from "node:test";
import assert from "node:assert/strict";
describe("User API", () => {
let server;
before(async () => {
server = await startTestServer();
});
after(async () => {
await server.close();
});
it("사용자 목록을 반환한다", async () => {
const res = await fetch("http://localhost:3000/api/users");
const users = await res.json();
assert.equal(res.status, 200);
assert.ok(Array.isArray(users));
assert.ok(users.length > 0);
});
it("새 사용자를 생성한다", async () => {
const res = await fetch("http://localhost:3000/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: "박테스트",
email: "test@example.com"
})
});
assert.equal(res.status, 201);
const user = await res.json();
assert.equal(user.name, "박테스트");
assert.ok(user.id > 0);
});
it("모킹 테스트", async () => {
const mockFetch = mock.fn(async () => ({
ok: true,
json: async () => ({ data: "mocked" })
}));
// mock 사용
const result = await mockFetch();
assert.equal(mockFetch.mock.callCount(), 1);
assert.deepEqual(await result.json(), { data: "mocked" });
});
});
# 테스트 실행
node --test test/
# 커버리지 리포트
node --test --experimental-test-coverage test/
# 워치 모드
node --test --watch test/
# 특정 패턴만 실행
node --test --test-name-pattern="사용자" test/
5. Glob 패턴 지원 (node:fs)
import { glob, globSync } from "node:fs";
// 비동기 glob
const jsFiles = await glob("**/*.js", { cwd: "./src" });
for await (const file of jsFiles) {
console.log(file);
}
// 동기 glob
const configs = globSync("*.config.{js,ts,json}");
console.log("설정 파일:", configs);
6. require()로 ESM 로딩
Node.js 22부터 CommonJS에서 require()로 ES 모듈을 로드할 수 있다.
// CommonJS 파일에서 ESM 패키지 사용 가능
const chalk = require("chalk"); // chalk는 ESM-only 패키지
console.log(chalk.blue("이제 require로 ESM을 로드할 수 있다"));
// 단, top-level await를 사용하는 모듈은 불가
마이그레이션 체크리스트
Node.js 22로 업그레이드할 때 확인할 사항을 정리한다.
- nvm이나 fnm으로 Node.js 22 LTS 설치:
nvm install 22 - package.json의 engines 필드 업데이트:
"engines": { "node": ">=22" } - tsconfig.json의 target을 ES2024 이상으로 변경
- 서드파티 TypeScript 런타임(ts-node, tsx) 제거 검토
- 내장 테스트 러너로 전환 검토 (Jest/Mocha 대체)
- ws 패키지를 내장 WebSocket으로 대체 검토
Node.js 22는 TypeScript 네이티브 지원과 Permission Model이라는 두 가지 핵심 기능으로 개발 생산성과 보안을 동시에 강화했다. 특히 별도 빌드 도구 없이 TypeScript를 직접 실행할 수 있다는 점은 개발 워크플로우를 크게 단순화한다.