On this page
Node and npm Compatibility
Deno is Node-compatible: most Node.js projects will run in Deno with little or
no change. You can import npm packages with the npm: specifier, use Node's
built-in modules through node: specifiers, and keep your existing
package.json. No npm install step is required before running your code.
import chalk from "npm:chalk@5";
console.log(chalk.green("Hello from npm in Deno"));
deno run main.ts
As of Deno 2.8, over 75% of Node's own test suite passes in Deno, covering
nearly every node: module. You can track the current state at
node-test-viewer.deno.dev and browse the
list of supported Node.js APIs.
That's all you really need to know to get started. The rest of this page covers the details, along with 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.
Using Node's 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());
Run it with deno run main.mjs and you will 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 the node: prefix:
import * as os from "os";
console.log(os.cpus());
$ deno run main.mjs
error: Import "os" not a dependency
hint: If you want to use a built-in Node module, add a "node:" prefix (ex. "node:os").
at file:///main.mjs:1:21
The same hints and additional quick-fixes are provided by the Deno LSP in your editor.
The node:module built-in includes the
registerHooks() API, which you can use to
customize module resolution and loading from inside your program.
Using npm packages Jump to heading
Deno has native support for importing npm packages by using npm: specifiers,
which have the following format:
npm:[@][/]
For example:
import * as emoji from "npm:node-emoji";
console.log(emoji.emojify(`:sauropod: :heart: npm`));
Can be run with:
$ deno run main.js
🦕 ❤️ npm
No npm install is necessary before the deno run command and no
node_modules folder is created: dependencies are stored in Deno's global
cache. These packages are also subject to the same
permissions as other code in Deno. If your
tools expect a local node_modules directory, see
node_modules.
npm specifiers also allow functionality that may be familiar from the npx
command.
# npx allows remote execution of a package from npm or a URL
$ npx create-next-app@latest
# deno run allows remote execution of a package from various locations,
# and can scoped to npm via the `npm:` specifier.
$ deno run -A npm:create-next-app@latest
For examples with popular libraries, please refer to the tutorial section.
First-class package.json support Jump to heading
Deno understands package.json in your project. You can:
- Declare dependencies there (alongside or instead of inline
npm:specifiers). - Use scripts from
package.jsonviadeno task(for example,deno task start). - Rely on
package.jsonfields liketypewhen resolving modules (see CommonJS support).
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 encounter in the wild and how to use them in Deno:
process- Deno provides theprocessglobal, which is by far the most popular global used in popular npm packages. It is available to all code. Deno can also guide you towards importing it explicitly fromnode:process. Opt in by enabling theno-process-globallint rule (off by default since Deno 2.8);deno lintwill then flag uses of the global:
console.log(process.versions.deno);
$ deno run process.js
2.8.3
$ deno lint process.js
error[no-process-global]: 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://docs.deno.com/lint/rules/no-process-global
Found 1 problem (1 fixable via --fix)
Checked 1 file
-
require()- see CommonJS support -
Buffer- to useBufferAPI it needs to be explicitly imported from thenode:buffermodule:
import { Buffer } from "node:buffer";
const buf = new Buffer(5, "0");
For TypeScript users needing Node.js-specific types like BufferEncoding, these
are available through the NodeJS namespace when using @types/node:
/// <reference types="npm:@types/node" />
// Now you can use NodeJS namespace types
function writeToBuffer(data: string, encoding: NodeJS.BufferEncoding): Buffer {
return Buffer.from(data, encoding);
}
Prefer using
Uint8Array
or other
TypedArray
subclasses instead.
-
__filename- useimport.meta.filenameinstead. -
__dirname- useimport.meta.dirnameinstead. -
setTimeout/setInterval- starting in Deno 2.8, the global timer functions return a Node.jsTimeoutobject instead of a number, matching Node.js semantics. The returned object exposes methods like.ref(),.unref(),.refresh(), and.hasRef(). It still coerces to a number (viaSymbol.toPrimitive), so existing code that stores the timer ID as a number or passes it toclearTimeout/clearIntervalcontinues to work unchanged.const t = setTimeout(() => {}, 1000); t.unref(); // don't keep the event loop alive for this timer clearTimeout(t);
CommonJS support Jump to heading
Deno supports CommonJS modules by default. 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 (see node_modules).
Deno's permission system also applies to CommonJS code. You typically need the
-R (--allow-read) and -E (--allow-env) flags, because Deno probes
package.json files and the node_modules directory to resolve CommonJS
modules.
How Deno determines module type Jump to heading
If the file extension is .cjs, Deno will treat the module as CommonJS, without
consulting package.json:
const express = require("express");
Deno will also attempt to load .js, .jsx, .ts, and .tsx files as
CommonJS if there's a package.json file with the "type": "commonjs" option
next to the file, or up in the directory tree when in a project with a
package.json file:
{
"type": "commonjs"
}
const express = require("express");
Tools like Next.js's bundler and others will generate a package.json file like
that automatically. If you have an existing project that uses CommonJS modules,
you can make it work with both Node.js and Deno by adding the
"type": "commonjs" option to the package.json file.
Deno does not otherwise analyze module contents to detect CommonJS, because
looking for package.json files on the file system and analyzing a module to
detect if it's CommonJS takes longer than not doing it, and to discourage the
use of CommonJS. You can opt into this detection by running with the
--unstable-detect-cjs flag in Deno >= 2.1.2. It takes effect except when
there's a package.json file with { "type": "module" }.
Using require() Jump to heading
To run the .cjs example above, install the dependency and pass the read and
env permission flags:
$ cat deno.json
{
"nodeModulesDir": "auto"
}
$ deno install npm:express
Dependencies:
+ npm:express 5.2.1
$ deno run -R -E main.cjs
[Function: createApplication] {
application: {
init: [Function: init],
defaultConfiguration: [Function: defaultConfiguration],
...
}
}
In ES modules, you can create an instance of the require() function manually:
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.
CommonJS and ESM interop Jump to heading
Deno's require() implementation supports 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. In other words, you can only require()
ES modules that are "synchronous".
export function greet(name) {
return `Hello ${name}`;
}
import { greet } from "./greet.js";
export { greet };
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
You can also import CommonJS files in ES modules:
module.exports = {
hello: "world",
};
import greet from "./greet.cjs";
console.log(greet);
$ deno run main.js
{ hello: "world" }
Troubleshooting Jump to heading
Deno will guide you when a file looks like CommonJS but isn't loaded as such. If
you see an error about module not being defined, fix it by one of the
following:
- Rewrite the code to ESM
- Change the file extension to
.cjs - Add a nearby
package.jsonwith{ "type": "commonjs" } - Run with
--unstable-detect-cjs
Conditional exports Jump to heading
Package exports can be conditioned on the resolution mode. The conditions satisfied by an import from a Deno ESM module are as follows:
["deno", "node", "import", "module-sync", "default"]
This means that the first condition listed in a package export whose key equals
any of these strings will be matched. For require() resolution, including
createRequire(), the conditions are:
["require", "node", "module-sync", "default"]
Deno also applies module-sync when analyzing CommonJS modules that re-export
through require().
You can expand the import conditions list using the --conditions CLI flag:
deno run --conditions development,react-server main.ts
[
"development",
"react-server",
"deno",
"node",
"import",
"module-sync",
"default"
]
Importing types Jump to heading
Many npm packages ship with types, you can import these and use them with types directly:
import chalk from "npm:chalk@5";
Some packages do not ship with types but you can specify their types with the
@ts-types directive. For example, using a
@types
package:
// @ts-types="npm:@types/express@^4.17"
import express from "npm:express@^4.17";
Module resolution Jump to heading
The official TypeScript compiler tsc supports different
moduleResolution
settings. Deno only supports the modern node16 resolution. Unfortunately many
npm packages fail to correctly provide types under node16 module resolution,
which can result in deno check reporting type errors, that tsc does not
report.
If a default export from an npm: import appears to have a wrong type (with the
right type seemingly being available under the .default property), it's most
likely that the package provides wrong types under node16 module resolution for
imports from ESM. You can verify this by checking if the error also occurs with
tsc --module node16 and "type": "module" in package.json or by consulting
the Are the types wrong? website
(particularly the "node16 from ESM" row).
If you want to use a package that doesn't support TypeScript's node16 module resolution, you can:
- Open an issue at the issue tracker of the package about the problem. (And perhaps contribute a fix :) (Although, unfortunately, there is a lack of tooling for packages to support both ESM and CJS, since default exports require different syntaxes. See also microsoft/TypeScript#54593)
- Use a CDN, that rebuilds the
packages for Deno support, instead of an
npm:identifier. - Ignore the type errors you get in your code base with
// @ts-expect-erroror// @ts-ignore.
Including Node types Jump to heading
Starting in Deno 2.8, deno check and the LSP include lib.node in every
type-check by default, so Node ambient types like Buffer, NodeJS.Timeout,
and process resolve without any configuration:
// 2.8+: type-checks with no extra setup
const buf: Buffer = Buffer.from("hello");
const t: NodeJS.Timeout = setTimeout(() => {}, 0);
The bundled lib.node tracks the major version of @types/node that matches
the Node release Deno reports in process.versions.node. If you need to pin a
specific @types/node version (for example to match the Node version your
project standardizes on), add it as an explicit dependency:
{
"imports": {
"@types/node": "npm:@types/node@^22"
}
}
On versions before 2.8, or if you've opted out of lib.node, you can still load
the types with a reference directive:
/// <reference types="npm:@types/node" />
Run npm binaries Jump to heading
You can run npm CLI tools (packages with bin entries) directly without
npm install by using an npm: specifier:
npm:[@][/]
For example:
$ deno run -R npm:cowsay@1.5.0 "Hello there!"
______________
< Hello there! >
--------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
$ deno run -R npm:cowsay@1.5.0/cowthink "What to eat?"
______________
( What to eat? )
--------------
o ^__^
o (oo)\_______
(__)\ )\/\
||----w |
|| ||
node_modules Jump to heading
When you run npm install, npm creates a node_modules directory in your
project which houses the dependencies as specified in the package.json file.
By default, Deno instead uses
npm specifiers to resolve npm
packages to a central global npm cache and does not create a node_modules
directory. This uses less space, keeps your project directory clean, and is the
recommended setup for new Deno projects.
There may however be cases where you need a local node_modules directory in
your Deno project, even if you don't have a package.json (eg. when using
frameworks like Next.js or Svelte or when depending on npm packages that use
Node-API).
Choosing a node_modules mode Jump to heading
| Mode | When to use | How to enable |
|---|---|---|
| none | Most Deno projects; keep repo clean | Default; do nothing |
| auto | Tools/bundlers expect node_modules; Node-API | "nodeModulesDir": "auto" or --node-modules-dir=auto |
| manual | Existing package.json with install step | "nodeModulesDir": "manual" + run deno install/npm/pnpm |
We recommend that you use the default none mode, and fall back to auto or
manual mode if you get errors about missing packages inside the node_modules
directory.
Automatic node_modules creation Jump to heading
If you need a node_modules directory in your project, you can use the
--node-modules-dir=auto flag on a per-command basis, or the
"nodeModulesDir": "auto" option in the config file, to tell Deno to create a
node_modules directory in the current working directory:
deno run --node-modules-dir=auto main.ts
or with a configuration file:
{
"nodeModulesDir": "auto"
}
The auto mode automatically installs dependencies into the global cache and creates a local node_modules directory in the project root. This is recommended for projects that have npm dependencies that rely on the node_modules directory: mostly projects using bundlers, or ones that have npm dependencies with postinstall scripts.
Manual node_modules creation Jump to heading
If your project has a package.json file, you can use the manual mode, which
requires an installation step to create your node_modules directory:
deno install
deno run --node-modules-dir=manual main.ts
or with a configuration file:
{ "nodeModulesDir": "manual" }
You would then run deno install/npm install/pnpm install or any other package
manager to create the node_modules directory.
Manual mode is the default mode for projects using a package.json. You may
recognize this workflow from Node.js projects. It is recommended for projects
using frameworks like Next.js, Remix, Svelte, Qwik etc, or tools like Vite,
Parcel or Rollup.
node_modules layout: isolated vs hoisted Jump to heading
When a local node_modules directory exists, Deno can lay it out in two ways.
The default (isolated) installs each package into a content-addressed
.deno/ directory and exposes it through a symlink, so every package only sees
its declared dependencies. This is similar to pnpm's layout.
node_modules/
├── .deno/chalk@5.6.2/node_modules/chalk/ ← real files
└── chalk -> .deno/chalk@5.6.2/node_modules/chalk
Some npm tooling, and any package that walks node_modules looking for
flat-resolved siblings, assumes the hoisted layout that npm and Yarn classic
use. Deno 2.8 adds a hoisted mode
(denoland/deno#32788) you can opt
into with nodeModulesLinker in deno.json. The hoisted linker requires a
manually-managed node_modules directory, so set nodeModulesDir to manual:
{
"nodeModulesDir": "manual",
"nodeModulesLinker": "hoisted"
}
Or as a one-off CLI flag (also requiring --node-modules-dir=manual):
deno install --node-modules-dir=manual --node-modules-linker=hoisted
In hoisted mode the most-depended-upon version of each package is placed at the
top of node_modules/, and conflicting versions are nested under the dependent
that needs them, just like npm:
node_modules/
├── chalk/ ← real files
├── express/
├── ms/ ← hoisted: most commonly needed version
└── debug/
└── node_modules/
└── ms/ ← nested: a different version
Stick with the default isolated mode unless a tool you depend on requires the hoisted layout. Isolated mode catches phantom dependencies that hoisted layouts hide.
Node-API addons Jump to heading
Deno supports Node-API addons used by
popular npm packages like esbuild,
npm:sqlite3 and
npm:duckdb. You can expect packages
that use public Node-APIs to work.
As of Deno 2.0, npm packages using Node-API addons are supported when a local
node_modules/ directory is present. Configure
"nodeModulesDir": "auto" | "manual" in deno.json or run with
--node-modules-dir=auto|manual.
Like all native FFI, pass --allow-ffi to grant explicit permission. Review
Security and permissions.
Many addons rely on npm lifecycle scripts (for example, postinstall). Deno
supports them, but they are not run by default for security reasons. See the
deno install docs.
Migrating from Node to Deno Jump to heading
Running a Node.js project with Deno usually requires little to no change. See the Migrating from Node.js to Deno guide for the details, optional toolchain improvements, and a Node-to-Deno command cheatsheet.
Private registries Jump to heading
Deno supports private registries, which allow you to host and share your own modules. This is useful for organizations that want to keep their code private or for individuals who want to share their code with a select group of people.
What are private registries? Jump to heading
Large organizations often host their own private npm registries to manage internal packages securely. These private registries serve as repositories where organizations can publish and store their proprietary or custom packages. Unlike public npm registries, private registries are accessible only to authorized users within the organization.
How to use private registries with Deno Jump to heading
First, configure your
.npmrc file to point
to your private registry. The .npmrc file must be in the project root or
$HOME directory. Add the following to your .npmrc file:
@mycompany:registry=http://mycompany.com:8111/
//mycompany.com:8111/:_authToken=secretToken
Replace http://mycompany.com:8111/ with the actual URL of your private
registry and secretToken with your authentication token. _authToken is the
standard bearer-token form; registries that use legacy _auth credentials are
also supported (see the .npmrc features list below).
Then update Your deno.json or package.json to specify the import path for
your private package. For example:
{
"imports": {
"@mycompany/package": "npm:@mycompany/package@1.0.0"
}
}
or if you're using a package.json:
{
"dependencies": {
"@mycompany/package": "1.0.0"
}
}
Now you can import your private package in your Deno code:
import { hello } from "@mycompany/package";
console.log(hello());
and run it using the deno run command:
deno run main.ts
.npmrc configuration Jump to heading
Beyond the basic registry / token setup above, Deno reads several other .npmrc
fields. The ones most likely to matter:
-
Mutual-TLS authentication (Deno 2.8+):
certfileandkeyfilepoint at PEM files used to authenticate the client when the registry requires mTLS..npmrc//registry.mycompany.com/:certfile=/etc/deno/client.crt //registry.mycompany.com/:keyfile=/etc/deno/client.key -
emailon_authentries (Deno 2.8+): some legacy on-prem registries require anemailalongside the auth token..npmrc//registry.mycompany.com/:_auth=secretToken //registry.mycompany.com/:email=ci@mycompany.com -
min-release-age(Deno 2.8+): refuses to install package versions younger than the configured age. Useful as a default supply-chain guard for all installs. The same control is also available as the CLI flag--minimum-dependency-ageand theminimumDependencyAgefield indeno.json. See Minimum dependency age for the full picture..npmrcmin-release-age=3 -
NPM_CONFIG_REGISTRYenv var: overrides the registry set in.npmrc, matching npm's precedence (handy in CI when you want to redirect installs without editing the checked-in.npmrc).
file: and link: dependencies in published packages Jump to heading
Some published npm packages accidentally ship a file: or link: specifier in
their package.json that points at a path on the publisher's machine:
{
"dependencies": {
"lodash": "^4.17.0",
"local-helpers": "file:../local-helpers"
}
}
Starting in Deno 2.8, those file: and link: entries are silently skipped
while resolving npm metadata, so packages that carry a stray local-path
dependency install cleanly instead of failing with an "Invalid version
requirement" error.