On this page
Migrating from Node.js to Deno
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.
To migrate an existing Node.js project to Deno, there are a number of differences to take into account between the Node and Deno runtimes. This guide will walk you through migrating your Node.js project to Deno.
Module imports and exports Jump to heading
Deno supports ECMAScript modules exclusively. If
your Node.js code uses
require
, you
should update it to use import
statements instead. If your internal code uses
CommonJS-style exports, those will also need to be updated.
A typical CommonJS-style project might look similar to this:
module.exports = function addNumbers(num1, num2) {
return num1 + num2;
};
const addNumbers = require("./add_numbers");
console.log(addNumbers(2, 2));
To convert it to ECMAScript modules, we'll make a few minor changes:
export function addNumbers(num1, num2) {
return num1 + num2;
}
import { addNumbers } from "./add_numbers.js";
console.log(addNumbers(2, 2));
Exports:
CommonJS | ECMAScript modules |
---|---|
module.exports = function add() {} |
export default function add() {} |
exports.add = function add() {} |
export function add() {} |
Imports:
CommonJS | ECMAScript modules |
---|---|
const add = require("./add_numbers"); |
import add from "./add_numbers.js"; |
const { add } = require("./add_numbers") |
import { add } from "./add_numbers.js" |
Quick fix with VSCode Jump to heading
If you are using VSCode you can use the built-in feature to convert CommonJS to
ES6 modules. Right-click on the require
statement, or the lightbulb icon and
select Quick Fix
and then Convert to ES module
.
CommonJS vs ECMAScript resolution Jump to heading
An important distinction between the two module systems is that ECMAScript
resolution requires the full specifier including the file extension to be
present. Omitting the file extension or having special handling of index.js
is
a feature unique to CommonJS. The benefit of the ECMAScript resolution is that
it works the same across the browser, Deno and other runtimes.
CommonJS | ECMAScript modules |
---|---|
"./add_numbers" |
"./add_numbers.js" |
"./some/directory" |
"./some/directory/index.js" |
Deno can add all the missing file extensions for you by running
deno lint --fix
. Deno's linter comes with a no-sloppy-imports
rule that will
show a linting error when an import path doesn't contain the file extension.
Node.js built-ins Jump to heading
In Node.js 20 and earlier, built-in modules in the Node.js standard library
could be imported with "bare specifiers". Consider the Node program below with a
.mjs
extension:
import * as os from "os";
console.log(os.cpus());
The os
module is built in to the
Node.js runtime, and can be imported using a bare specifier as above.
The .mjs
file extension is supported but not required in Deno. Because Node
doesn't support ESM by default, it requires you to name any files that use ESM
with a .mjs
file extension.
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.
For example - if you update the code above to be this instead:
import * as os from "node:os";
console.log(os.cpus());
And run it with deno run index.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.
Runtime permissions in Deno Jump to heading
Consider the following simple express server:
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 it with deno run server.js
, it would prompt you for a number of
permissions required to execute the code and its dependencies. DENO SECURE BY
DEFAULT.
$ 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 like it would in Node.js, you can pass the -A
flag to enable
all permissions.
deno run -A server.js
For more granular permissions you can enable access to specific features by opting into permission individually.
Running scripts from package.json
Jump to heading
You can continue to use your existing npm scripts with Deno, by using the
deno task
subcommand. Consider the following
Node.js project with a script called start
inside its package.json
.
{
"name": "my-project",
"scripts": {
"start": "eslint"
}
}
You can execute this script with Deno by running:
deno task start
Node.js global objects Jump to heading
In Node.js, there are a number of
global objects that are available in the
scope of all programs, like the process
object, Buffer
or __dirname
and
__filename
.
Deno does not add additional objects and variables to the global scope, other
than the Deno
global. Any API that doesn't exist
as a web standard browser API will be found on Deno
, or you can import Node.js
built-in modules using the node:
specifier.
import process from "node:process";
import { Buffer } from "node:buffer";
const __filename = import.meta.filename;
const __dirname = import.meta.dirname;
If you do run into a problem with Node.js compatibility, please let us know by opening an issue on GitHub.
Optional improvements - 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 tasks, dependencies, path
mappings, and other runtime configurations.
Migrating npm scripts to deno.json
Jump to heading
If preferred, you can move your npm scripts over to deno.json
to manage your
project's runtime configuration. This allows you to run all of the necessary
permission flags and other runtime config in one place, which can then be run
with deno task
.
{
"tasks": {
"dev": "deno run --allow-net --allow-read --allow-env server.js"
}
}
deno task dev
Migrating npm dependencies to deno.json
Jump to heading
You can also migrate your dependencies over to deno.json
to manage your
project's dependencies. Deno supports importing dependencies from external
package repositories, local files and urls. To import your npm dependencies, you
can add them to the imports
field in deno.json
, and add the npm:
specifier
to the import path:
{
"imports": {
"express": "npm:express@4"
}
}
Deno supports multiple package registries and allows you to import dependencies from npm, JSR and HTTP URLs.
{
"imports": {
"express": "npm:express@4",
"@luca/cases": "jsr:@luca/cases@1",
"foo": "https://example.com/foo.ts"
}
}
Optional improvements - Linting Jump to heading
Deno ships with a built-in linter that is written with performance in mind. It can lint large projects in 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 it 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 rules come with the ability to fix the detected issues
automatically when 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.
Optional improvements - Formatting Jump to heading
Another optional improvement you can make is to format your code with Deno. Deno ships with a built-in formatter that can format your code according to the Deno style guide. You can run the formatter on your project by running:
deno fmt
On your CI you can run the formatter with the --check
argument, to make it
exit with an error code when the code is not formatted properly.
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.
Optional improvements - 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.
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.