Since its announcement, Deno has generated quite a lot of interest within the JavaScript community. As a JavaScript runtime designed by the creator of Node, you might expect there to be be a lot of similarities between the two projects, and there are. However, there are also important differences, meaning you can’t just substitute one for the other.
This article will take a look at Deno in relation to its “older cousin” to help understand what they have in common, and what sets them apart. (If you want to get the skinny on Deno first, check out our recent introduction.)
Language Support
Both projects are JavaScript runtimes, allowing JavaScript code to be executed on a computer outside of a web browser. Let’s look at how they stack up in terms of language support.
Node.js
The current LTS release of Node (v12.18.1 as of writing) supports modern JavaScript syntax and features. It also supports about 75% of the ES2020 spec. ECMAScript modules are also supported, but are currently only classed as experimental: you need to use the .mjs
file extension, or add the property "type": "module"
to your project’s package.json
file.
In order to run TypeScript (or any other language) on Node, the code needs to be compiled to JavaScript that the V8 engine can execute. There are several different ways to do this, with different pros and cons, so getting up and running means having to choose one of these and follow the necessary setup process.
Deno
I was unable to find any mention of the JavaScript spec supported by Deno, but as it also uses V8 under the hood I would assume a similar level of support as in Node. My own tests show that Deno supports ES2020 features like Promise.allSettled()
and the globalThis
keyword. ECMAScript modules are the default, with CommonJS modules not supported unless you use the Node compatibility library (more about this later).
TypeScript is supported as a first-class language in Deno, meaning that it works out-of-the-box: no installing additional tools to transpile to JavaScript first. Of course, the V8 engine doesn’t support TypeScript natively, so Deno is still transpiling the code under the hood, but this is all seamless and transparent to you as a developer.
I also couldn’t find mention of which version of TypeScript Deno v1.0.1 uses, but it supports optional chaining and nullish coalescing (but not private class fields) which would peg it as TS 3.7.
APIs
Deno and Node both expose their own APIs to developers, allowing us to write programs that can actually do useful things like read and write files, and send and receive network requests.
Node.js
When Node was first released, there was no built-in support for Promises. As a result of this, most of the APIs for asynchronous operations were written to take an error-first callback:
const fs = require('fs');
fs.readFile('readme.txt', (err, data) => {
if (err) {
// Handle the error
}
// Otherwise handle the data
});
Even though Node developers now have access to Promises and the async/await syntax, the APIs still expect callbacks in order to maintain backwards compatibility.
Deno
Deno’s API has been designed to take advantage of modern JavaScript features. All the asynchronous methods return Promises. Deno also supports top level await, meaning you can use await in your main script without having to wrap it in an async function.
try {
const data = await Deno.readFile('readme.txt');
// Handle the data
} catch (e) {
// Handle the error
}
The development team also made the decision to use web standards where possible, which means they’ve implemented browser APIs where it’s practical to do so. Deno provides a global window
object, and APIs such as addEventListener
and fetch
. Having access to fetch
is particularly nice, as with Node you’d have to polyfill this or use a third-party library.
The compatibility module
Deno provides a compatibility layer with the aim of allowing you to reuse existing Node packages. It’s not yet complete, but it does currently support loading CommonJS modules via require()
, among other things.
Package Management
Package management is one area where Deno represents a radical departure from Node’s way of doing things. As it’s still early days for Deno, it remains to be seen if its approach will prove to be advantageous.
Node.js
As you might be aware, Node comes with its own package manager called npm, which is used to install and manage third-party packages. npm is mainly used with the online npm registry, where most of the available third-party packages are listed.
When you use npm to install a package into your project, a package.json
file is used to specify the package name and acceptable version range. The package itself (plus any packages it depends on) are then downloaded into a node_modules
folder inside your project.
Deno
Deno does away with the need for a package manager altogether. Instead, packages are linked to directly via a URL:
import { Response } from "https://deno.land/std@0.53.0/http/server.ts";
On the first run of your code, Deno fetches and compiles all the dependencies. They are then cached on the file system, separately from your project, so subsequent runs are much faster.
Similar to npm’s package-lock.json
file, Deno allows you to specify a lock file that will be used to ensure that only dependencies that match the exact version you originally imported will be used
Third-party Packages
A language can thrive or die on the vibrancy of its ecosystem, as productivity relies on not having to reinvent the wheel! Here, it seems that Node currently has the edge.
Node.js
Node has a large and varied ecosystem of libraries and packages available. In the 11 years since its release, over a million packages have been registered on the npm registry. Of course, the quality can vary a lot, and many are no longer actively maintained, but it’s still a big plus for Node developers.
Deno
As we saw in the previous section, Deno is actively trying to avoid the need for a package manager or registry, by allowing scripts to import modules directly from any public URL. Of course, it’s hard to import packages if you don’t know what’s out there, so the Deno website maintains a list of compatible third-party modules. As of writing, there are 707 modules in the list.
Deno’s standard library
One way that Deno attempts to improve the developer experience is by providing a standard library of helpers and utilities for common tasks. All modules are audited by the core developers to ensure high-quality, dependable code. There are modules for things like processing command-line arguments, and colorizing terminal output — both things that are only available as third-party packages for Node.
Security
Perhaps one of Deno’s most touted improvements over Node is the permissions system. Let’s look at why.
Node.js
The Node runtime is very permissive, allowing code full access to the computer’s network and file system. There’s the potential for third-party code to wreak havoc on your system if unchecked.
Deno
Improving the security model is something that Ryan Dahl specifically set out to do when designing Deno. By default, all code is executed in a secure sandbox environment. This prevents code from having access to things like the file system, network, and environment variables unless access is specifically granted with a command-line argument.
# Allow script to make network requests
deno run --allow-net server.ts
Even better, when allowing read or write access to the file system, or access to the network, you can supply a whitelist. This means you could restrict a Deno program’s read/write access to the project’s data
folder, for example, limiting any potential malicious damage.
Deno: Batteries Included
Before we wrap up, I just wanted to talk about one more thing. If you take a browse through the tools section of the manual, you’ll notice that Deno provides us with some nice “bonus features”! The following are built-in tools to make the developer experience that little bit nicer:
- bundler: bundles up a specified script and its dependencies into a single file
- debugger: allows debugging your Deno programs with Chrome Devtools, VS Code, and other tools (note: Node also comes with a debugger)
- dependency inspector: running this on an ES module will list out all of the dependencies in a tree
- documentation generator: parses JSDoc annotations in a given file and outputs documentation
- formatter: auto-formats both JavaScript and TypeScript code
- test runner: you can use this for testing your JS and TS code, in conjunction with the assertions module in the standard library
- linter: a code linter (currently unstable) to help catch potential issues in your programs
Verdict
The purpose of this article is not to advocate for either Node or Deno, but rather to compare and contrast the two. You should now have an understanding of the similarities between the two runtimes and, perhaps more importantly, the differences.
Deno presents some particular advantages to developers, including a robust permissions system and first-class TypeScript support. The design decisions and additional built-in tooling are aimed at providing a productive environment and a good developer experience.
Node, on the other hand, has a massive and well-established ecosystem around it that’s been over a decade in the making. This, along with the plethora of documentation and tutorials out there, probably makes Node.js a safe bet for some time to come.
Deno Foundations
Get up to speed with Deno. Our Deno Foundations collection helps you take your first steps into the Deno world and beyond, and we’re adding to it constantly. We’ll bring you the tutorials you need to become a pro. You can always refer to our index as it’s updated at the end of our Introduction to Deno: