On this page
Node and npm Compatibility
- Deno is Node-compatible. Most Node projects will run in Deno with little or no change!
- Deno supports npm packages. Just use the
npm:specifier in the import, and Deno takes care of the rest.
For example, here's how you'd import Hono from npm in a Deno project:
import { Hono } from "npm:hono";
That's all you really need to know to get started! 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.
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.
We provide a list of supported Node.js APIs that you can use in Deno.
Quick start Jump to heading
Import an npm package Jump to heading
import chalk from "npm:chalk@5";
console.log(chalk.green("Hello from npm in Deno"));
deno run main.ts
Execute CommonJS Jump to heading
Use .cjs extension to inform Deno that module is using CommonJS system.
const chalk = require("chalk");
console.log(chalk.green("Hello from npm in Deno"));
deno run main.cjs
Use a Node API Jump to heading
import path from "node:path";
console.log(path.join("./foo", "../bar"));
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());
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:
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
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.
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. These packages are also subject to the same
permissions as other code in Deno.
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 below).
By default, dependencies are stored in Deno's global cache without creating a
local node_modules directory. If your tools expect node_modules, opt-in
using nodeModulesDir in deno.json.
npm specifiers have the following format:
npm:[@][/]
This also allows 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.
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):
console.log(process.versions.deno);
$ deno run process.js
2.0.0
$ 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.
Note: Deno's permission system still applies to CommonJS code. You may need
--allow-read because Deno probes package.json and node_modules to resolve
CommonJS modules.
Use .cjs extension Jump to heading
If the file extension is .cjs Deno will treat this module as CommonJS.
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.
You can also just run a .cjs file directly:
deno run -A main.cjs
package.json type option Jump to heading
Deno will attempt to load .js, .jsx, .ts, and .tsx files as CommonJS if
there's a package.json file with "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 "type": "commonjs" option to the
package.json file.
Always detecting if a file might be CommonJS Jump to heading
Telling Deno to analyze modules as possibly being CommonJS is possible by
running with the --unstable-detect-cjs in Deno >= 2.1.2. This will take
effect, except when there's a package.json file with { "type": "module" }.
Looking for package.json files on the file system and analyzing a module to detect if its CommonJS takes longer than not doing it. For this reason and to discourage the use of CommonJS, Deno does not do this behavior by default.
Create require() manually Jump to heading
An alternative option is to 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.
require(ESM) 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 - or 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
Import CommonJS modules Jump to heading
You can also import CommonJS files in ES modules.
module.exports = {
hello: "world",
};
import greet from "./greet.js";
console.log(greet);
$ deno run main.js
{
"hello": "world"
}
Hints and suggestions 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 to ESM
- Change the file extension to
.cjs - Add a nearby
package.jsonwith{ "type": "commonjs" } - Run with
--unstable-detect-cjs
See docs: CommonJS in Deno
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", "default"]
This means that the first condition listed in a package export whose key equals
any of these strings will be matched. You can expand this list using the
--conditions CLI flag:
deno run --conditions development,react-server main.ts
["development", "react-server", "deno", "node", "import", "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 standardises 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 --allow-read npm:cowsay@1.5.0 "Hello there!"
______________
< Hello there! >
--------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
$ deno run --allow-read 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.
Deno uses npm specifiers to
resolve npm packages to a central global npm cache, instead of using a
node_modules folder in your projects. This is ideal since it uses less space
and keeps your project directory clean.
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
- Use no local node_modules (default) when your project runs fine with Deno's global cache. No setup required.
- Use auto when some tools expect node_modules or you rely on Node-API addons and want automatic creation.
- Use manual when your project has a package.json and you prefer an explicit install step.
| 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 |
Default Deno dependencies behavior Jump to heading
By default, Deno will not create a node_modules directory when you use the
deno run command, dependencies will be installed into the global cache. This
is the recommended setup for new Deno projects.
Automatic node_modules creation Jump to heading
If you need a node_modules directory in your project, you can use the
--node-modules-dir flag or 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 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.
We recommend that you use the default none mode, and fallback to auto or
manual mode if you get errors about missing packages inside the node_modules
directory.
node_modules flag Jump to heading
You can also enable the creation of a node_modules directory on a per-command
basis with the --node-modules-dir flag.
import chalk from "npm:chalk@5";
console.log(chalk.green("Hello"));
deno run --node-modules-dir main.ts
Running the above command, with a --node-modules-dir flag, will create a
node_modules folder in the current directory with a similar folder structure
to npm.
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
Summary: Node-API addons work in Deno when a local node_modules/ is present
and you grant --allow-ffi.
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.
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.