Skip to main content
workerd’s Node.js compatibility layer implements a subset of Node.js APIs. Due to architectural differences and the Workers runtime environment, some APIs behave differently or have limitations compared to Node.js.

Runtime environment constraints

workerd operates in a different environment than Node.js, which affects API availability and behavior.

No filesystem access

workerd does not have direct access to a traditional filesystem. The node:fs module is partially implemented but with significant limitations:
  • File operations are restricted or non-functional
  • Paths do not correspond to a real filesystem
  • Many filesystem APIs will throw errors or return stub values
Do not rely on filesystem operations in workerd. Use alternative storage mechanisms like KV, R2, or Durable Objects.

No child processes

The node:child_process module is a non-functional stub. You cannot spawn child processes or execute shell commands in workerd.
import { spawn } from 'node:child_process';

// This will throw an error or be a no-op
spawn('ls', ['-la']);

No threads

The node:worker_threads module is a non-functional stub. workerd uses isolates for concurrency instead of threads. For concurrent execution, use:
  • Multiple Workers with service bindings
  • Durable Objects for stateful concurrent operations
  • Standard Web Workers API (where available)

Limited networking

Network modules like node:net, node:dgram, and node:tls have limited functionality:
  • TCP and UDP sockets are restricted
  • TLS is available primarily through the fetch API and Web Crypto
  • Direct socket access may not work as in Node.js
Use the standard fetch() API for HTTP/HTTPS requests instead of node:http or node:https.

Process and environment

process.env behavior

The process.env object behaves differently in workerd:
  • Requires the nodejs_compat_populate_process_env flag to be populated
  • Environment variables come from worker bindings, not the system environment
  • Some environment variables common in Node.js (like NODE_ENV) may not be set
import process from 'node:process';

// May be empty or contain only worker-specific values
console.log(process.env);

process properties

Many process properties are stubs or provide workerd-specific values:
  • process.platform returns a fixed value (not the actual host OS)
  • process.arch returns a fixed value
  • process.version reflects workerd’s Node.js compatibility version, not Node.js itself
  • process.pid, process.ppid may return stub values
  • process.cwd(), process.chdir() are limited or non-functional

No process signals

Signal handling APIs like process.on('SIGTERM') are non-functional. Worker lifecycle is managed by workerd, not through signals.

Module system differences

Module resolution

workerd uses its own module system with some differences from Node.js:
  • Module resolution follows workerd’s rules, not Node.js’s node_modules algorithm
  • Some Node.js-specific module resolution features may not work
  • The node: prefix is required for Node.js built-ins unless nodejs_compat_v2 is enabled

Conditional imports

Package exports conditions may behave differently:
  • workerd looks for workerd, worker, and browser conditions
  • The node condition is not typically matched
  • This can affect which code paths in packages are used

Global objects

Global differences

workerd prioritizes Web Platform Standard globals over Node.js globals:
// Buffer and process are NOT global
import { Buffer } from 'node:buffer';
import process from 'node:process';

console.log(typeof Buffer);   // "undefined" without import
console.log(typeof process);  // "undefined" without import

require() and __dirname

CommonJS-specific globals are not available:
  • require() is not available in ES modules (use import instead)
  • __dirname and __filename are not defined
  • module.exports is not available (use export instead)
workerd uses ES modules by default. Some CommonJS interoperability is provided through module wrapping.

Crypto differences

Web Crypto takes precedence

workerd implements the Web Crypto API (crypto.subtle) as the primary crypto interface. The node:crypto module is built on top of the same underlying implementation (BoringSSL) but may have subtle differences:
  • Algorithm names may differ between Web Crypto and Node.js crypto
  • Key formats and import/export behavior may vary
  • Some Node.js-specific crypto APIs may be missing
// Prefer Web Crypto API when possible
const key = await crypto.subtle.generateKey(
  { name: 'AES-GCM', length: 256 },
  true,
  ['encrypt', 'decrypt']
);

// Node.js crypto API is also available
import { createHash } from 'node:crypto';
const hash = createHash('sha256').update('data').digest('hex');

Streams differences

workerd provides both Node.js streams and Web Streams:

Web Streams are preferred

The standard ReadableStream, WritableStream, and TransformStream from the Streams API are the primary stream interfaces. Node.js streams are provided for compatibility but may have subtle differences:
  • Performance characteristics may differ
  • Backpressure handling may work differently
  • Some advanced Node.js stream features may be missing

Stream interoperability

Conversion between Node.js streams and Web Streams is supported:
import { Readable } from 'node:stream';

// Convert Node.js Readable to Web ReadableStream
const webStream = Readable.toWeb(nodeStream);

// Convert Web ReadableStream to Node.js Readable
const nodeStream = Readable.fromWeb(webStream);

Module-specific differences

Buffer implementation

The Buffer class is fully implemented but built on Web Platform standards:
  • Backed by Uint8Array and ArrayBuffer
  • Performance characteristics may differ from Node.js
  • All standard Buffer methods are available

Events implementation

The EventEmitter class is provided but some advanced features may differ:
  • Event loop timing may behave differently
  • Error handling in event listeners may have subtle differences

Timers behavior

timer functions (setTimeout, setInterval) are implemented but subject to workerd’s execution model:
  • Timers do not prevent the isolate from being terminated
  • Timer precision may differ from Node.js
  • Very long timers may not work as expected

URL parsing

workerd uses the WHATWG URL standard via the ada-url library:
  • Fully spec-compliant URL parsing
  • Legacy Node.js URL APIs (url.parse(), url.format()) are available but may behave differently
  • Use the URL class for new code

Testing and debugging

Limited debugging tools

Node.js debugging tools do not work with workerd:
  • node:inspector is a non-functional stub
  • Chrome DevTools cannot attach to workerd isolates directly
  • console.log() is your primary debugging tool

Test runner differences

The node:test module is a stub. Use alternative testing frameworks:
  • Run tests in workerd using the workerd binary with test configurations
  • Use external test frameworks that support Workers
  • Write .wd-test files for workerd-specific testing

Performance considerations

Cold start optimization

workerd optimizes for fast cold starts, which affects implementation:
  • Some APIs may be lazily initialized
  • Large dependencies in Node.js modules may impact startup time
  • Minimize use of heavy Node.js APIs in global scope

Memory constraints

Workers run in isolates with memory limits:
  • Large buffers or data structures may hit memory limits
  • Node.js APIs that allocate significant memory may fail
  • Monitor memory usage with worker metrics

Compatibility flag requirements

Many Node.js modules require explicit compatibility flags:
compatibilityFlags = ["nodejs_compat"]
Modules enabled by flags:
ModuleFlag
node:http, node:httpsenable_nodejs_http_modules
node:_http_serverenable_nodejs_http_server_modules
node:fsenable_nodejs_fs_module
node:osenable_nodejs_os_module
node:http2enable_nodejs_http2_module
node:consoleenable_nodejs_console_module
node:vmenable_nodejs_vm_module
node:perf_hooksenable_nodejs_perf_hooks_module
node:sqliteenable_nodejs_sqlite_module
See the supported APIs page for a complete list.

Best practices

To maximize compatibility and avoid issues:
1

Prefer Web Platform APIs

Use standard Web Platform APIs like fetch(), Web Crypto, and Web Streams instead of Node.js equivalents when possible.
2

Test in workerd

Always test your code in workerd, not just Node.js. Behavior may differ in subtle ways.
3

Avoid filesystem and process APIs

Do not rely on node:fs, node:child_process, or process management APIs. They will not work as expected.
4

Use appropriate storage

Instead of filesystem operations, use:
  • KV for key-value storage
  • R2 for object storage
  • Durable Objects for stateful operations
  • D1 for SQL databases
5

Check compatibility flags

Ensure you have the necessary compatibility flags enabled for the Node.js modules you use.
6

Handle missing APIs gracefully

Wrap Node.js API usage in try-catch blocks or check for existence before use:
let fs;
try {
  fs = await import('node:fs');
} catch (e) {
  // Handle missing fs module
  console.log('Filesystem not available');
}

Migration from Node.js

When migrating Node.js applications to workerd:
  1. Identify Node.js API usage: Audit your code for Node.js built-in module imports
  2. Check supported APIs: Verify that the modules you need are supported (see supported APIs)
  3. Replace unsupported APIs: Find alternatives for unsupported or limited APIs
  4. Enable compatibility flags: Add necessary flags to your configuration
  5. Test thoroughly: Run your application in workerd and test all code paths
  6. Monitor for errors: Watch for runtime errors related to missing or limited APIs

Getting help

If you encounter issues with Node.js compatibility:
  • Check the supported APIs page to verify the API is implemented
  • Review this differences page for known limitations
  • Test your code in workerd to reproduce the issue
  • Report issues to the workerd project with minimal reproduction cases

Build docs developers (and LLMs) love