Published on

Node.js 22 Features Every Backend Engineer Must Know

Authors

Introduction

Node.js 22 released in April 2024 as the new LTS release. It includes features that reshape how you write backends: native SQLite without dependencies, a mature test runner replacing Jest, built-in WebSocket support, and experimental TypeScript support. This guide covers what changed, migration paths, and production impact.

Built-In SQLite

Node.js 22 includes native SQLite:

import { DatabaseSync } from 'node:sqlite'

const db = new DatabaseSync('data.db')

// Create table
db.exec(`
  CREATE TABLE users (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    email TEXT UNIQUE NOT NULL
  )
`)

// Insert
const stmt = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)')
stmt.run('Alice', 'alice@example.com')

// Query
const user = db.prepare('SELECT * FROM users WHERE id = ?').get(1)
console.log(user)

// Close
db.close()

No dependencies. No installation. SQLite is built into Node.

Performance is native-speed (<1ms queries). Perfect for caching, local persistence, and edge functions.

Compare to sqlite3 npm package:

// Old way (sqlite3 package)
import Database from 'better-sqlite3'

const db = new Database('data.db')
const stmt = db.prepare('SELECT * FROM users WHERE id = ?')
const user = stmt.get(1)

Node.js 22 eliminates the dependency. Code is nearly identical.

Built-In Test Runner (No Jest Needed)

Jest has been the testing standard, but Node.js now ships a production-ready test runner:

// test/users.test.ts
import { describe, it, expect, beforeEach, afterEach } from 'node:test'
import { createUser, getUser } from '../src/users.js'

describe('User Management', () => {
  beforeEach(() => {
    // Setup
  })

  afterEach(() => {
    // Cleanup
  })

  it('creates a user', () => {
    const user = createUser('Alice', 'alice@example.com')
    expect(user.name).toBe('Alice')
  })

  it('fetches a user by ID', async () => {
    const user = await getUser(1)
    expect(user).toBeDefined()
  })

  describe('validation', () => {
    it('rejects invalid email', () => {
      expect(() => createUser('Bob', 'invalid')).toThrow()
    })
  })
})

Run with:

node --test test/**/*.test.ts

Features:

  • Describe/it syntax matching Jest
  • Async/await support
  • Mocking via node:test/mock
  • Sub-test nesting
  • Parallel execution

No config file. No babel plugins. Just native Node.

Built-In --watch Mode

Reload on file changes:

node --watch server.ts

The runtime watches for changes and restarts automatically. Perfect for development.

No nodemon, no tsx, no dependencies.

Require ESM Modules in CommonJS

Load ES modules from CJS:

// index.cjs
const chalk = require('chalk')
console.log(chalk.blue('Hello'))

// This now works if chalk is ESM
// Previously required workarounds or dual builds

Node.js 22 allows CJS code to require ESM packages. Eases migration from CommonJS to ESM.

Built-In WebSocket Client

Connect to WebSocket servers natively:

const ws = new WebSocket('wss://echo.websocket.org')

ws.addEventListener('open', () => {
  ws.send('Hello')
})

ws.addEventListener('message', (event) => {
  console.log('Received:', event.data)
  ws.close()
})

ws.addEventListener('error', (event) => {
  console.error('WebSocket error:', event)
})

No ws package dependency. WebSocket is a standard API.

Perfect for:

  • Real-time updates from external services
  • Subscriptions to WebSocket APIs
  • Bi-directional communication

Access language and locale settings:

console.log(navigator.language)  // 'en-US'
console.log(navigator.languages) // ['en-US', 'en']

Enables server-side localization without external libraries.

--experimental-strip-types for TypeScript

Run TypeScript directly without compilation:

node --experimental-strip-types src/server.ts

Node parses .ts files, strips type annotations, and executes immediately. No build step, no ts-node, no tsx.

Limitations:

  • Decorators require --enable-source-maps
  • Some TS features (enums) still require transpilation
  • Runtime performance is slightly lower than pre-compiled

For development, this eliminates workflow friction.

// server.ts
import express from 'express'
import { User } from './types.ts'

const app = express()

interface RequestBody {
  name: string
  email: string
}

app.post('/users', (req, res) => {
  const body: RequestBody = req.body
  // Process...
  res.json({ id: 1, ...body })
})

app.listen(3000)

Run with:

node --experimental-strip-types server.ts

No compilation, no build artifacts.

Permission Model (Stable)

Fine-grained permissions prevent malicious code:

# Allow only network access
node --allow-fs-read=/app --allow-net=localhost:3000 app.js

# Deny file system writes
node --deny-fs-write app.js

# Deny environment variable access
node --deny-env=DATABASE_URL app.js

Permissions default to deny. Explicitly grant what code needs:

// app.js
import fs from 'node:fs'

// Throws if --allow-fs-read not granted
fs.readFileSync('secret.txt')

Production use-case: Run third-party npm packages in sandbox. Malicious code cannot exfiltrate secrets or modify files.

V8 12.4 Performance Improvements

V8 (JavaScript engine) in Node.js 22 includes:

  • 10-15% faster JSON parsing via new JSONParse bytecode
  • 5% faster object operations from optimized inline caching
  • Improved garbage collection reducing pause times
  • Better regex performance with adaptive engines

Throughput benchmarks:

Node.js 20: 28,000 req/s (HTTP server)
Node.js 22: 32,000 req/s (+14%)

Real-world impact: Your existing code gets faster for free.

Migration Path From Node 20

Step 1: Update Node.js

nvm install 22
nvm use 22
npm install  # Re-install for Node 22 binaries

Step 2: Remove test framework (optional)

If using Jest, switch to Node''s test runner:

# Remove Jest
npm remove jest ts-jest @types/jest

# Update package.json
{
  "scripts": {
    "test": "node --test test/**/*.test.ts",
    "test:watch": "node --test --watch test/**/*.test.ts"
  }
}

Step 3: Update type-checking setup (optional)

If using tsx or ts-node:

# Old
npx ts-node server.ts

# New (Node 22)
node --experimental-strip-types server.ts

Or keep tsx—it''s still faster for large projects.

Step 4: Test thoroughly

npm test
npm run build
npm run start

Migration is low-risk. Most code runs unchanged.

Try-Catch Error Handling Improvements

No breaking changes, but stack traces are cleaner:

try {
  const result = await fetchData()
} catch (err) {
  if (err instanceof TypeError) {
    // Error classification is faster in V8 12.4
  }
}

Stack traces in async code are more accurate.

Production Readiness

Node.js 22 is production-ready. Many major companies run it. LTS support extends to April 2027.

Considerations:

  • Startup time: ~5% faster than Node 20
  • Memory usage: Similar to Node 20
  • Break-compat risk: Very low (minor releases are safe)
  • Security updates: Regular, until April 2027

For new projects, Node.js 22 is the default. Migrating from Node 20 is safe and recommended.

Feature Adoption Timeline

Immediately adopt:

  • Built-in SQLite (replaces sqlite3/better-sqlite3)
  • Test runner (replaces Jest)
  • --watch mode

In 2-3 months:

  • WebSocket client (when fully stable)
  • Permission model (after security audit)

In 6 months:

  • --experimental-strip-types (when mature)

Checklist

  • Update to Node.js 22 LTS
  • Migrate tests to node:test runner
  • Remove Jest and related packages
  • Replace sqlite3 with node:sqlite
  • Test WebSocket functionality
  • Configure permission model for security
  • Enable --watch for development
  • Benchmark performance improvements
  • Update CI/CD to Node 22
  • Document migration for team

Conclusion

Node.js 22 is the most feature-rich LTS release in years. By bundling SQLite, test runners, and TypeScript support, it eliminates entire categories of dependencies. Performance is measurably better. Migration is low-risk. For backend engineers, Node.js 22 is the clear upgrade path. The ecosystem is consolidating around Node''s built-ins, and adopting early gives you a competitive advantage.