Node.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를 직접 실행할 수 있다는 점은 개발 워크플로우를 크게 단순화한다.