Deno vs Node.js: a comparison

7 min read Original article ↗

How Deno is different from Node

Single Executable File

Deno ships as a single executable with no dependencies and comes with some built-in tools to make the developer experience easier:

  1. bundler (deno bundle)
  2. debugger (--inspect, --inspect-brk)
  3. dependency inspector (deno info)
  4. documentation generator (deno doc)
  5. formatter (deno fmt)
  6. test runner (deno test)
  7. linter (deno lint) - currently unstable

All these tools are standardised for Deno, so you have sure that they will have full support.

Being a single executable, Deno can update itself via: deno upgrade or deno upgrade --version <new_version>.

This will fetch the specified version (or latest if unspecified) and replace your current executable with it.

It's possible to have multiple versions installed, using version managers.

For Node, version managers are also responsible for updating/installing new versions, e.g nvm.

First Class TypeScript

Deno is based on TypeScript, so it supports it out of the box without the need to install or configure tools, like adding a tsconfig.json configuration file in Node. Although it comes with a default config file, it's possible to add your own deno run -c tsconfig.json [file-to-run.ts].

Since TypeScript is just a superset of JavaScript, Deno can also run it.

To run a script, you can create a hello-world.ts file with the snippet above and run it by executing: deno run hello-world.ts. This will use Microsoft’s TypeScript compiler to check types and produce JavaScript and then run the script.

Right now, Deno's team assumes that the time it takes V8 to parse TypeScript compared to JavaScript is much higher and it's an ongoing task to port the TypeScript Compiler to Rust.

Security

One of the main focuses is security. Deno is secured by default and the code is executed in a secure sandbox, replicating the same permission model that the browser implements. Unless specified, it has no access to the filesystem, network, or environmental variables, and the access permissions need to be explicitly passed to the Deno process on the command line.


If you try to run the previous snippet:


To avoid the error, you need to add the --allow-env flag to be able to have access to the environmental variables:


There's an option to allow all access --allow-all or -A, but this is not recommended.

Diversely, Node is very permissive, you have full access to pretty much everything:


Modules

Deno uses ES Modules, the official standard format to package JavaScript code, introduced in ES6/ES2015:

When Node was created, JavaScript didn't have it's own module system, so it used the CommonJS standard:

Right now Node only has experimental support for ES Modules and you need to do some configurations/changes to existing files.

I will not go into detail about the differences, but if you want an in-depth look at both module systems I recommend reading this.

Although not being compatible, in general, with Node packages, Deno provides a Node Compatibility Library, that will allow us to use some NPM packages that do not use non-polyfilled Node APIs.

One of the main advantages is that uses a standard browser-compatible protocol for loading modules, allowing you to import modules directly via URLs, which has a huge impact in the way we handle modules.

New call-to-action

Package Management

Considering that Deno uses URLs for loading modules, it explicity takes on the role of both runtime and package manager. It’s not dependent on a centralised server for modules distribution.This means it requires fully qualified module names, including the extension (or a server providing the correct media type).

You can import modules directly and use dependencies as code:


To run the previous snippet, you can just execute: deno run --allow-net http-server.ts. There is no install to do beforehand because Deno downloads and caches a module in a global directory the first time its URL is encountered in a script. The modules cache has two main goals:

  • offline work: if the cache already exists for a specific module it uses it. You can add the —reload flag if you want to manually reset the cache.
  • single copy: every specific module version that is imported, only has a copy, no matter how many projects reference it.

All modules and files loaded from remote URLs, apart from being cachable are also intended to be immutable.

As you probably noticed, imports require fully qualified module names, including the extension. Besides, Deno has no "magical" module resolution. Instead, imported modules are specified as files (including extensions) or fully qualified URL imports.

Node uses npm as the package manager to install and manage third-party packages listed in the npm registry. This makes linking to external libraries fundamentally centralized through the npm registry. When you install a package into your project with NPM/Yarn, a package.json file is used to specify the package name and accepted versions, and the packages are downloaded into a node_modules folder inside your project. As a result, node_modules folders become huge, since modules require specific versions of other modules, and they will be replicated in every single project directory it requires.

Deno does not use NPM for dependency management, so no package.json and no node_modules

Standard Modules

Deno provides curated standard modules of helpers and utilities for common tasks that are audited by the Deno Core team.

These modules will be tagged in accordance with Deno releases. Not specifying a tag will link automatically to the master branch, so it's recommended that you link to tagged versions to avoid unintended updates and breaking changes.

In case you want to lock a specific module version, you can use a lock.json file, to check module integrity.

Third Party Modules

Deno provides a hosting service integrity for Deno scripts. It caches releases of open source modules stored on GitHub and serves them at one easy to remember domain.

For instance, if you are looking for a date utility you can use the https://deno.land/x search function that helps you find modules. Once you’ve found the module you want, in this case, date-fns, you can just import the correct URL in the file you’re using it in:


For all the other NPM packages, if they are not compatible with esmodule imports, you can use a service like JSPM which will resolve the 3rd party modules and compile the CommonJS modules to work as ES Modules imports.

Promises

Deno uses promises all the way down - all asynchronous methods return Promises:


From the previous snippet, you can see that Deno supports top-level await. This allows us to use the await syntax in the global scope without having to wrap it in the async function. Recently Node Node also added support for top-level await.

Long before Promises or async/await, Node's API for asynchronous operations were designed to use callbacks and follow the error callback convention:


Even though Node developers now have access to Promises and the async/await syntax, Node's API still expect callbacks to maintain backward compatibility.

Probably one big difference is that Deno always dies immediately on unhandled promises, where Node currently handles rejections by emitting a deprecation warning to stderr. Right now, Node is running a survey to better understand the way users are dealing with this.

Browser compatibility

The development team also decided to use browser APIs where it’s practical to do so. Like so, Deno provides a global window object, and APIs such as addEventListener, onload, onunload and fetch.

Deno provides a global window object, and APIs such as addEventListener, onload, onunload and fetch.

This means that Deno programs written completely in JavaScript which do not use the global Deno namespace (or feature test for it), ought to be isomorphic, meaning they will be able to be run in a modern web browser without any change:


While the window object is not available in Node, you can use fetch if you polyfill this or use a third-party library.