Node.js 22 LTS Overview
Node.js 22 was promoted to LTS (Long Term Support) in October 2024. Codenamed “Jod”, it will receive active support until April 2027.
| Version | Release | LTS Start | End |
|---|---|---|---|
| Node.js 18 | 2022/04 | 2022/10 | 2025/04 |
| Node.js 20 | 2023/04 | 2023/10 | 2026/04 |
| Node.js 22 | 2024/04 | 2024/10 | 2027/04 |
| Node.js 24 | 2025/04 | 2025/10 | 2028/04 |
Recommended: Use LTS versions in production
require() ESM Support
The biggest highlight of Node.js 22 is the ability to load ES modules using require() from CommonJS.
// ESM package (package.json: "type": "module")
// lib.mjs
export const greeting = 'Hello';
export function sayHello(name) {
return `${greeting}, ${name}!`;
}
export default { greeting, sayHello };
// Now loadable from CommonJS
// app.cjs
const { greeting, sayHello } = require('./lib.mjs');
console.log(sayHello('World')); // Hello, World!
// Default export also available
const lib = require('./lib.mjs');
console.log(lib.default.greeting); // Hello
Promotion from Experimental Flag
# Before Node.js 22 (experimental)
node --experimental-require-module app.js
# Node.js 22.12.0+ (enabled by default)
node app.js
# To disable
node --no-experimental-require-module app.js
Caveats
// ESM with top-level await cannot be required
// async-lib.mjs
const data = await fetch('https://api.example.com/data');
export const config = await data.json();
// This will error
try {
require('./async-lib.mjs'); // ERR_REQUIRE_ASYNC_MODULE
} catch (e) {
console.error('Cannot require modules with top-level await');
}
WebSocket Client (Built-in)
// Node.js 22+: WebSocket available without external libraries
const ws = new WebSocket('wss://echo.websocket.org');
ws.addEventListener('open', () => {
console.log('Connected');
ws.send('Hello, WebSocket!');
});
ws.addEventListener('message', (event) => {
console.log('Received:', event.data);
});
ws.addEventListener('close', () => {
console.log('Disconnected');
});
ws.addEventListener('error', (error) => {
console.error('Error:', error);
});
HTTP Server Integration
import { createServer } from 'http';
import { WebSocketServer } from 'ws';
const server = createServer();
const wss = new WebSocketServer({ server });
wss.on('connection', (ws, request) => {
console.log('New connection:', request.url);
ws.on('message', (message) => {
// Broadcast
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(message.toString());
}
});
});
});
server.listen(8080, () => {
console.log('WebSocket server running on ws://localhost:8080');
});
V8 Engine 12.4
Node.js 22 ships with V8 12.4, enabling the latest JavaScript features.
Array.fromAsync
// Create array from async iterable
async function* asyncGenerator() {
yield 1;
yield 2;
yield 3;
}
const arr = await Array.fromAsync(asyncGenerator());
console.log(arr); // [1, 2, 3]
// Also handles Promise arrays
const promises = [
fetch('/api/user/1'),
fetch('/api/user/2'),
fetch('/api/user/3'),
];
const responses = await Array.fromAsync(promises, (p) => p.then((r) => r.json()));
Set Method Additions
const a = new Set([1, 2, 3, 4]);
const b = new Set([3, 4, 5, 6]);
// Union
console.log(a.union(b)); // Set {1, 2, 3, 4, 5, 6}
// Intersection
console.log(a.intersection(b)); // Set {3, 4}
// Difference
console.log(a.difference(b)); // Set {1, 2}
// Symmetric difference
console.log(a.symmetricDifference(b)); // Set {1, 2, 5, 6}
// Subset check
console.log(new Set([1, 2]).isSubsetOf(a)); // true
console.log(a.isSupersetOf(new Set([1, 2]))); // true
// Disjoint check
console.log(a.isDisjointFrom(new Set([7, 8]))); // true
Iterator Helpers
// Transform operations on iterators
function* numbers() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
}
// map, filter, take available
const result = numbers()
.filter((n) => n % 2 === 0)
.map((n) => n * 2)
.take(2)
.toArray();
console.log(result); // [4, 8]
// drop: skip from beginning
const skipped = numbers().drop(2).toArray();
console.log(skipped); // [3, 4, 5]
// flatMap
function* nested() {
yield [1, 2];
yield [3, 4];
}
const flat = nested()
.flatMap((arr) => arr)
.toArray();
console.log(flat); // [1, 2, 3, 4]
// find
const found = numbers().find((n) => n > 3);
console.log(found); // 4
// some / every
console.log(numbers().some((n) => n > 3)); // true
console.log(numbers().every((n) => n > 0)); // true
Built-in SQLite (Experimental)
import { DatabaseSync } from 'node:sqlite';
// In-memory database
const db = new DatabaseSync(':memory:');
// Create table
db.exec(`
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE
)
`);
// Insert data
const insert = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)');
insert.run('Alice', 'alice@example.com');
insert.run('Bob', 'bob@example.com');
// Execute query
const select = db.prepare('SELECT * FROM users WHERE name = ?');
const user = select.get('Alice');
console.log(user); // { id: 1, name: 'Alice', email: 'alice@example.com' }
// Get all
const all = db.prepare('SELECT * FROM users').all();
console.log(all);
// Transaction
const transfer = db.transaction((from, to, amount) => {
db.prepare('UPDATE accounts SET balance = balance - ? WHERE id = ?').run(amount, from);
db.prepare('UPDATE accounts SET balance = balance + ? WHERE id = ?').run(amount, to);
});
transfer(1, 2, 100);
Performance Improvements
| Metric | Node.js 20 | Node.js 22 | Improvement |
|---|---|---|---|
| Startup time (Hello World) | 120ms | 85ms | 29% faster |
| HTTP server (req/sec) | 45,000 | 52,000 | 15% faster |
| File I/O (1000 file reads) | 280ms | 230ms | 18% faster |
| Stream processing | 320ms | 250ms | 22% faster |
Watch Mode Stabilization
# Watch files and auto-restart
node --watch server.js
# Watch specific paths only
node --watch-path=./src --watch-path=./config server.js
# Preserve output during watch
node --watch --watch-preserve-output server.js
// Programmatic watch status check
import { watch } from 'node:fs/promises';
const ac = new AbortController();
const { signal } = ac;
(async () => {
const watcher = watch('./src', { recursive: true, signal });
for await (const event of watcher) {
console.log(`${event.eventType}: ${event.filename}`);
}
})();
// Stop watching
// ac.abort();
Native Glob Pattern Support
import { glob, globSync } from 'node:fs';
// Async API
const files = await glob('**/*.js', {
cwd: './src',
ignore: ['node_modules/**'],
});
console.log(files);
// Sync API
const syncFiles = globSync('**/*.{ts,tsx}');
console.log(syncFiles);
// Also available via fs module
import { promises as fs } from 'node:fs';
const matches = await fs.glob('src/**/*.test.ts');
Environment Variable File Loading
# Auto-load .env file
node --env-file=.env app.js
# Multiple files supported
node --env-file=.env --env-file=.env.local app.js
# .env
DATABASE_URL=postgresql://localhost:5432/mydb
API_KEY=secret-key
NODE_ENV=development
// Access environment variables
console.log(process.env.DATABASE_URL);
console.log(process.env.API_KEY);
Test Runner Improvements
// test/user.test.js
import { describe, it, before, after, mock } from 'node:test';
import assert from 'node:assert/strict';
describe('User Service', () => {
let mockDb;
before(() => {
mockDb = mock.fn(() => ({ id: 1, name: 'Test User' }));
});
after(() => {
mock.reset();
});
it('should create a user', async () => {
const user = await createUser({ name: 'Test User' });
assert.strictEqual(user.name, 'Test User');
});
it('should validate email', () => {
assert.throws(() => createUser({ email: 'invalid' }), /Invalid email/);
});
// Snapshot testing
it('should match snapshot', async (t) => {
const user = await getUser(1);
t.assert.snapshot(user);
});
});
# Run tests
node --test
# With coverage
node --test --experimental-test-coverage
# Specific file only
node --test test/user.test.js
Migration Guide
# Check version
node --version
# Using nvm
nvm install 22
nvm use 22
nvm alias default 22
# Check dependency compatibility
npx npm-check-updates --target minor
# package.json engine specification
# "engines": { "node": ">=22" }
Reference Links
- Node.js 22 Release Notes
- require(esm) Documentation
- Node.js 22 Breaking Changes
- V8 12.4 Release Notes