Skip to main content
On this page

Node.js support

Modern Node.js projects will run in Deno with little to no reworking required. However, there are some key differences between the two runtimes that you can take advantage of to make your code simpler and smaller when migrating your Node.js projects to Deno.

Node built-in modules Jump to heading

Deno provides a compatibility layer that allows the use of Node.js built-in APIs within Deno programs. However, in order to use them, you will need to add the node: specifier to any import statements that use them:

import * as os from "node:os";
console.log(os.cpus());

And run it with deno run main.mjs - you will notice you get the same output as running the program in Node.js.

Updating any imports in your application to use node: specifiers should enable any code using Node built-ins to function as it did in Node.js.

To make updating existing code easier, Deno will provide helpful hints for imports that don't use node: prefix:

main.mjs
import * as os from "os";
console.log(os.cpus());
$ deno run main.mjs
error: Relative import path "os" not prefixed with / or ./ or ../
  hint: If you want to use a built-in Node module, add a "node:" prefix (ex. "node:os").
    at file:///main.mjs:1:21

Same hints and additional quick-fixes are provided by the Deno LSP in your editor.

CommonJS support Jump to heading

CommonJS is a module system that predates ES modules. While we firmly believe that ES modules are the future of JavaScript, there are millions of npm libraries that are written in CommonJS and Deno offers full support for them. Deno will automatically determine if a package is using CommonJS and make it work seamlessly when imported:

main.js
import react from "npm:react";
console.log(react);
$ deno run -E main.js
18.3.1

npm:react is a CommonJS package. Deno allows to import it as it was an ES module.

Deno strongly encourages use of ES modules in your code, and offers CommonJS support with following restrictions:

Use .cjs extension Jump to heading

If the file extension is .cjs Deno will treat this module as CommonJS.

main.cjs
const express = require("express");

Deno does not look for package.json files and type option to determine if the file is CommonJS or ESM.

When using CommonJS, Deno expects that dependencies will be installed manually and a node_modules directory will be present. It's best to set "nodeModulesDir": "auto" in your deno.json to ensure that.

$ cat deno.json
{
  "nodeModulesDir": "auto"
}

$ deno install npm:express
Add npm:express@5.0.0

$ deno run -R -E main.cjs
[Function: createApplication] {
  application: {
    init: [Function: init],
    defaultConfiguration: [Function: defaultConfiguration],
    ...
  }
}

-R and -E flags are used to allow permissions to read files and environment variables. permissions.

Deno's permission system is still in effect when using CommonJS modules. It is necessary to provide at least --allow-read permission as Deno will probe the file system for package.json files and node_modules directory to properly resolve CommonJS modules.

Create require() manually Jump to heading

An alternative option is to create an instance of the require() function manually:

main.js
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
const express = require("express");

In this scenario the same requirements apply, as when running .cjs files - dependencies need to be installed manually and appropriate permission flags given.

require(ESM) Jump to heading

Deno's require() implementation support requiring ES modules.

This works the same as in Node.js, where you can only require() ES modules that don't have Top-Level Await in their module graph - or in other words you can only require() ES modules that are "synchronous".

greet.js
export function greet(name) {
  return `Hello ${name}`;
}
esm.js
import { greet } from "./greet.js";

export { greet };
main.cjs
const esm = require("./esm");
console.log(esm);
console.log(esm.greet("Deno"));
$ deno run -R main.cjs
[Module: null prototype] { greet: [Function: greet] }
Hello Deno

import "./index.cjs" Jump to heading

You can also import CommonJS files in ES modules, provided that these files use .cjs extension.

Deno does not look for package.json files and type option to determine if the file is CommonJS or ESM.

greet.cjs
module.exports = {
  hello: "world",
};
main.js
import greet from "./greet.js";
console.log(greet);
$ deno run main.js
{
  "hello": "world"
}

Notice that in this example no permission flags were specified - when importing CJS from ES modules, Deno can staticaly analyze and find relevant modules without having to probe file system at runtime.

Hints and suggestions Jump to heading

Deno will provide useful hints and suggestions to guide you towards working code when working with CommonJS modules.

As an example, if you try to run a CommonJS module that doesn't have .cjs extension you might see this:

main.js
module.exports = {
  hello: "world",
};
$ deno run main.js
error: Uncaught (in promise) ReferenceError: module is not defined
module.exports = {
^
    at file:///main.js:1:1

    info: Deno does not support CommonJS modules without `.cjs` extension.
    hint: Rewrite this module to ESM or change the file extension to `.cjs`.

Node.js global objects Jump to heading

In Node.js, there are a number of global objects available in the scope of all programs that are specific to Node.js, eg. process object.

Here are a few globals that you might enounter in the wild and how to use them in Deno:

  • process - Deno provides the process global, which is by far the most popular global used in popular npm packages. It is available to all code. However, Deno will guide you towards importing it explicitly from node:process module by providing lint warnings and quick-fixes:
process.js
console.log(process.versions.deno);
$ deno run process.js
2.0.0
$ deno lint process.js
error[no-process-globals]: NodeJS process global is discouraged in Deno
 --> /process.js:1:13
  |
1 | console.log(process.versions.deno);
  |             ^^^^^^^
  = hint: Add `import process from "node:process";`

  docs: https://lint.deno.land/rules/no-process-globals


Found 1 problem (1 fixable via --fix)
Checked 1 file
  • require() - see CommonJS support

  • Buffer - to use Buffer API it needs to be explicitly imported from the node:buffer module:

buffer.js
import { Buffer } from "node:buffer";

const buf = new Buffer(5, "0");

Prefer using Uint8Array or other TypedArray subclasses instead.

  • __filename - use import.meta.filename instead.

  • __dirname - use import.meta.dirname instead.

Node-API addons Jump to heading

Deno supports Node-API addons that are used by popular npm packages like esbuild, npm:sqlite3 or npm:duckdb.

You can expect all packages that use public and documented Node-APIs to work.

Info

Most packages using Node-API addons rely on npm "lifecycle scripts", like postinstall.

While Deno supports them, they are not run by default due to security considerations. Read more in deno install docs.

As of Deno 2.0, npm packages using Node-API addons are only supported when a node_modules/ directory is present. Add "nodeModulesDir": "auto" or "nodeModulesDir": "manual" setting your deno.json file, or run with --node-modules-dir=auto|manual flag to ensure these packages work correctly. In case of misconfiguration Deno will provide hints how the situation can be resolved.

Runtime permissions in Deno Jump to heading

Consider the following simple Express server:

server.js
import express from "npm:express@4";

const app = express();

app.get("/", function (_req, res) {
  res.send("hello");
});

app.listen(3000, () => {
  console.log("Express listening on :3000");
});

If you run the above with deno run server.js, you will be prompted for permissions required to execute the code and its dependencies. For example:

$ deno run server.js
┌ ⚠️  Deno requests net access to "0.0.0.0:8000".
├ Requested by `Deno.listen()` API.
├ Run again with --allow-net to bypass this prompt.
└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all net permissions) >

Deno features runtime security by default, meaning that you as the developer must opt in to giving your code access to the filesystem, network, system environment, and more. Doing this prevents supply chain attacks and other potential vulnerabilities in your code. By comparison, Node.js has no concept of runtime security, with all code executed with the same level of permission as the user running the code.

To run your code as you would in Node.js, you can pass the -A flag to enable all permissions.

deno run -A server.js

For more granular control, you can enable access to specific features by opting in to individual permissions.

Running scripts from package.json Jump to heading

Deno supports running npm scripts natively with the deno task subcommand. Consider the following Node.js project with a script called start inside its package.json:

package.json
{
  "name": "my-project",
  "scripts": {
    "start": "eslint"
  }
}

You can execute this script with Deno by running:

deno task start

Migrating from Node.js to Deno Jump to heading

Running your Node.js project with Deno is a straightforward process. In most cases you can expect little to no changes to be required, if your project is written using ES modules.

Main points to be aware of, include:

  1. Importing Node.js built-in modules requires the node: specifier:
// ❌
import * as fs from "fs";
import * as http from "http";

// ✅
import * as fs from "node:fs";
import * as http from "node:http";

Tip

It is recommended to change these import specifiers in your existing project anyway. This is a recommended way to import them in Node.js too.

  1. Some globals available in Node.js need to be explicitly imported, eg. Buffer:
import { Buffer } from "node:buffer";
  1. require() is only available in files with .cjs extension, in other files an instance of require() needs to be created manually. npm dependencies can use require() regardless of file extension.

Optional improvements with Deno's built-in tools Jump to heading

One of Deno's core strengths is a unified toolchain that comes with support for TypeScript out of the box, and tools like a linter, formatter and a test runner. Switching to Deno allows you to simplify your toolchain and reduces the number of moving components in your project. Deno also has a more secure runtime, with runtime permissions that allow you to control what your code can access.

deno.json Jump to heading

Deno has its own config file, deno.json or deno.jsonc, which can be used to configure your project.

You can use it to define dependencies using the imports option - you can migrate your dependencies one-by-one from package.json, or elect to not define them in the config file at all and use npm: specifiers inline in your code.

In addition to specifying depenendencies you can use deno.json to define tasks, lint and format options, path mappings, and other runtime configurations.

Linting Jump to heading

Deno ships with a built-in linter that is written with performance in mind. It's similar to ESlint, though with a limited number of rules. If you don't rely on ESLint plugins, you can drop eslint dependency from devDependencies section of package.json and use deno lint instead.

Deno can lint large projects in just a few milliseconds. You can try it out on your project by running:

deno lint

This will lint all files in your project. When the linter detects a problem, it will show the line in your editor and in the terminal output. An example of what that might look like:

error[no-constant-condition]: Use of a constant expressions as conditions is not allowed.
 --> /my-project/bar.ts:1:5
  | 
1 | if (true) {
  |     ^^^^
  = hint: Remove the constant expression

  docs: https://lint.deno.land/rules/no-constant-condition


Found 1 problem
Checked 4 files

Many linting issues can be fixed automatically by passing the --fix flag:

deno lint --fix

A full list of all supported linting rules can be found on https://lint.deno.land/. To learn more about how to configure the linter, check out the deno lint subcommand.

Formatting Jump to heading

Deno ships with a built-in formatter that can optionally format your code according to the Deno style guide. Instead of adding prettier to your devDependencies you can instead use Deno's built-in zero-config code formatter deno fmt.

You can run the formatter on your project by running:

deno fmt

If using deno fmt in CI, you can pass the --check argument to make the formatter exit with an error when it detects improperly formatted code.

deno fmt --check

The formatting rules can be configured in your deno.json file. To learn more about how to configure the formatter, check out the deno fmt subcommand.

Testing Jump to heading

Deno encourages writing tests for your code, and provides a built-in test runner to make it easy to write and run tests. The test runner is tightly integrated into Deno, so that you don't have to do any additional configuration to make TypeScript or other features work.

my_test.ts
Deno.test("my test", () => {
  // Your test code here
});
deno test

When passing the --watch flag, the test runner will automatically reload when any of the imported modules change.

To learn more about the test runner and how to configure it, check out the deno test subcommand documentation.

Node to Deno Cheatsheet Jump to heading

Node.js Deno
node file.js deno file.js
ts-node file.ts deno file.ts
nodemon deno run --watch
node -e deno eval
npm i / npm install deno install
npm install -g deno install -g
npm run deno task
eslint deno lint
prettier deno fmt
package.json deno.json or package.json
tsc deno check ¹
typedoc deno doc
jest / ava / mocha / tap / etc deno test
nexe / pkg deno compile
npm explain deno info
nvm / n / fnm deno upgrade
tsserver deno lsp
nyc / c8 / istanbul deno coverage
benchmarks deno bench

¹ Type checking happens automatically, TypeScript compiler is built into the deno binary.

Node Compatibility Jump to heading

Deno provides polyfills for a number of built-in Node.js modules and globals. For a full list of Node built-in modules, see the reference.

Node compatibility is an ongoing project - help us identify gaps and let us know which modules you need by opening an issue on GitHub.

= Full support
= Partial support
= Stubs only

node:async_hooks Jump to heading

AsyncLocalStorage is supported. AsyncResource, executionAsyncId, and createHook are non-functional stubs.

node:cluster Jump to heading

All exports are non-functional stubs.

node:crypto Jump to heading

Missing Certificate class, crypto.Cipheriv.prototype.setAutoPadding, crypto.Decipheriv.prototype.setAutoPadding, crypto.publicDecrypt, crypto.ECDH.prototype.convertKey, x448 option for generateKeyPair, crypto.KeyObject, safe, add and rem options for generatePrime, crypto.Sign.prototype.sign and crypto.Verify.prototype.verify with non BinaryLike input, crypto.secureHeapUsed, crypto.setEngine, legacy methods of crypto.X509Certificate.

node:dgram Jump to heading

Some dgram.Socket instance methods are non-functional stubs:

  • addMembership
  • addSourceSpecificMembership
  • dropMembership
  • dropSourceSpecificMembership
  • setBroadcast
  • setMulticastInterface
  • setMulticastLoopback
  • setMulticastTtl
  • setTtl

node:dns Jump to heading

Missing dns.resolve* with ttl option.

node:domain Jump to heading

All exports are non-functional stubs. This is a deprecated Node module.

node:fs Jump to heading

node:fs Missing utf16le, latin1 and ucs2 encoding for fs.writeFile and fs.writeFileSync.

node:fs/promises Missing lchmod.

node:http Jump to heading

createConnection option is currently not supported.

node:http2 Jump to heading

Partially supported, major work in progress to enable grpc-js.

node:https Jump to heading

Missing https.Server.opts.cert and https.Server.opts.key array type.

node:inspector Jump to heading

console is supported. Other APIs are stubs and will throw an error.

node:module Jump to heading

The register() function is not supported.

node:net Jump to heading

Missing net.Socket.prototype.constructor with fd option.

node:perf_hooks Jump to heading

Missing perf_hooks.eventLoopUtilization, perf_hooks.timerify, perf_hooks.monitorEventLoopDelay.

node:process Jump to heading

Missing multipleResolves, worker events.

node:repl Jump to heading

builtinModules and _builtinLibs are supported. Missing REPLServer.prototype.constructor and start().

node:test Jump to heading

Currently only test API is supported.

node:tls Jump to heading

Missing createSecurePair.

node:trace_events Jump to heading

All exports are non-functional stubs.

node:util Jump to heading

Missing aborted, transferableAbortSignal, transferableAbortController, MIMEParams, MIMEType and getSystemErrorMap.

node:v8 Jump to heading

cachedDataVersionTag and getHeapStatistics are supported. setFlagsFromStrings is a noop. Other APIs are not supported and will throw an error.

node:vm Jump to heading

Partial support.

node:wasi Jump to heading

All exports are non-functional stubs.

node:worker_threads Jump to heading

Missing parentPort.emit, parentPort.removeAllListeners, markAsUntransferable, moveMessagePortToContext, receiveMessageOnPort, Worker.prototype.getHeapSnapshot.

Globals Jump to heading

This is the list of Node globals that Deno supports. These globals are only available in the npm package scope. In your own code you can use them by importing them from the relevant node: module.

Global name Status
AbortController
AbortSignal
Blob
Buffer
ByteLengthQueuingStrategy
__dirname ⚠️ Info
__filename ⚠️ Info
atob
BroadcastChannel
btoa
clearImmediate
clearInterval
clearTimeout
CompressionStream
console
CountQueuingStrategy
Crypto
CryptoKey
CustomEvent
CustomEvent
DecompressionStream
Event
EventTarget
exports
fetch
fetch
File
File
FormData
global
Headers
MessageChannel
MessageEvent
MessagePort
module
PerformanceEntry
PerformanceMark
PerformanceMeasure
PerformanceObserver
PerformanceObserverEntryList
PerformanceResourceTiming
performance
process
queueMicrotask
ReadableByteStreamController
ReadableStream
ReadableStreamBYOBReader
ReadableStreamBYOBRequest
ReadableStreamDefaultController
ReadableStreamDefaultReader
require
Response
Request
setImmediate
setInterval
setTimeout
structuredClone
structuredClone
SubtleCrypto
DOMException
TextDecoder
TextDecoderStream
TextEncoder
TextEncoderStream
TransformStream
TransformStreamDefaultController
URL
URLSearchParams
URLSearchParams
WebAssembly
WritableStream
WritableStreamDefaultController
WritableStreamDefaultWriter