Deno VS Node
By: Davide D'Antonio
Tags: Typescript, Deno, Nodejs, JavaScript, Javascript Development

What is Deno?

Deno is a TypeScript runtime based on V8, the Google’s JavaScript runtime; if you are familiar with Node.js, the popular server-side JavaScript ecosystem, you will understand that Deno is exactly the same. Except that it was designed with some improvements:

It is based on the modern functionality of the JavaScript language;

It has an extensive Standard library;

It supports TypeScript natively;

Supports EcmaScript modules;

It doesn’t have a centralized package manager like npm;

It has several built-in utilities such as a dependency inspector and a code formatter;

Aims to be as compatible with browsers as possible;

Security is the main feature.

What are the main differences with Node.js?

I think that Deno’s main goal is to replace Node.js. However, there are some important common characteristics. For example:

Both were created by Ryan Dahl;

Both were developed on Google’s V8 engine;

Both were developed to execute server-side JavaScript.

But on the other side there are some important differences:

Rust and TypeScript . Unlike Node.js which is written in C++ and JavaScript, Deno is written in Rust and TypeScript.

. Unlike Node.js which is written in C++ and JavaScript, Deno is written in Rust and TypeScript. Tokyo . Introduced in place of libuv as an event-driven asynchronous platform.

. Introduced in place of libuv as an event-driven asynchronous platform. Package Manager . Unlike Node.js, Deno doesn’t have a centralized package manager, so it is possible to import any ECMAScript module from a url.

. Unlike Node.js, Deno doesn’t have a centralized package manager, so it is possible to import any ECMAScript module from a url. ECMAScript . Deno uses modern ECMAScript functionality in all its APIs, while Node.js uses a standard callback-based library.

. Deno uses modern ECMAScript functionality in all its APIs, while Node.js uses a standard callback-based library. Security. Unlike a Node.js program which, by default, inherits the permissions from the system user that’s running the script, a Deno program runs in a sandbox. For example, the access to file system, to network resources, etc., must be authorized with a flag permission.

Installation

Deno is a single executable file without dependencies. We can install it on our machine by downloading the binary version from this page, or we can download and execute one of the installers listed below.

Shell (Mac, Linux)

$ curl -fsSL https://deno.land/x/install/install.sh | sh

PowerShell (Windows)

$ iwr https://deno.land/x/install/install.ps1 -useb | iex

Homebrew (Mac OS)

$ brew install deno

Let’s take a look at security

One of the main Deno’s features is the security. Compared to Node.js, Deno executes the source code in a sandbox, this mean that the runtime:

Doesn’t have access to the file system;

Doesn’t have access to the network resources;

Cannot excecute other scripts;

Doesn’t have access to environment variables.

Let’s make a simple example. Consider the following script:

async function main () {

const encoder = new TextEncoder ()

const data = encoder.encode ('Hello Deno! 🦕

')

await Deno.writeFile( 'hello.txt' , data)

}

main()

The script is really simple. It just create a text file named hello.txt that will contain the string Hello Deno 🦕. Really simple! Or not?

As we said before, the code will run in a sandbox and, obviously, it doesn’t have the access to the filesystem. Infact, if we execute the script with the following command:

$ deno run hello-world.ts

It will print on terminalsomething like:

Check file:///home/davide/denoExample/hello-world.ts

error: Uncaught PermissionDenied: write access to "hello.txt", run again with the --allow-write flag

at unwrapResponse ($deno$/ops/dispatch_json.ts:42:11)

at Object.sendAsync ($deno$/ops/dispatch_json.ts:93:10)

at async Object.open ($deno$/files.ts:38:15)

at async Object.writeFile ($deno$/write_file.ts:61:16)

at async file:///home/davide/projects/denoExample/hello-world.ts:5:3

As we can see, the error message is really clear. The file was not created on the filesystem because the script does not have the write permission to do that but, by adding the flag --allow-write :

$ deno run --allow-write hello-world.ts

the script will end without errors and the file hello.txt was created correctly in the current working directory.

In addition to the flag --allow-write that give to us the access to the filesystem, there are also other flags such as --allow-net that give to us the access to the network resources, or --allow-run that is useful to run external script or subprocess. We can find the complete permissions list at the following url https://deno.land/manual/getting_started/permissions.

A simple server

Now we will create a simple server that accept connections on port 8000 and return to the client the string Hello Deno .

// file server.ts

import { serve } from 'https://deno.land/std/http/server.ts' const s = serve({ port: 8000 })

console.log('Server listening on port :8000')

for await (const req of s) {

req.respond({ body: 'Hello Deno!

' })

}

Obviously to run the script we need to specify the --allow-net flag:

$ deno run --allow-net server.ts

In our terminal will appear something like:

Now if we open our favourite browser, or if we want to use the curl command, we can take a test to the URL http://localhost:8000 . The result will be something like this:

Modules

Just like browsers, Deno loads all his modules via URL. Many people are initially confused by this approach, but that make sense. Here an example:

import { assertEquals } from "https://deno.land/std/testing/asserts.ts";

Importing packages via URL has advantages and disvantages. The main advantages are:

more flexibility;

we can create a package without publish it in a public repository (like npm).

I think that a sort of package manager can be released in future, but nothing official has come out for now.

The official Deno website give to us the opportunity to host our source code, and then the distribution via URLs: https://deno.land/x/.

Importing packages via URLs, give to the developers the freedom they need to host their code wherever they want: the decentralization at best. Therefore, we don’t need a package.json file or the node_modules directory.

When the application start, all imported packages are downloaded, compiled, and stored a cache memory. If we want to download all the packages again we need to specify the flag --reload .

I need to type the URL every time? 🤯🤬

Deno support import maps natively. This mean that it’s possible to specify a special command flag like --importmap=<FILENAME> . Let’s take a look to a simple example.

Imagine that we have a file import_map.json , with the following content:

{

"imports": {

"fmt/": "https://deno.land/std@0.65.0/fmt/"

}

}

The file specifies that at /ftm key, of the imports object, correspond the URL https://deno.land/sdt@0.65.0/ftm/ and it can be used as follow:

// file colors.ts

import { green } from "fmt/colors.ts"; console.log(green("Hello Deno! 🦕"));

This feature is unstable at the moment, and we need to run our script color.ts using the flag --unstable , so:

$ deno run --unstable --importmap=import_map.json colors.ts

Now in our terminal appear something like this:

Versioning

The package versioning is a developer responsibility and, on the client side, we can decide to use a specific version in the URL of the package when we import it:

https://unpkg.com/package-name@0.0.5/dist/package-name.js

Ready to use utilities

Speaking honestly: the current state of JavaScript tools for developer is a real CHAOS! And when TypeScript ones are added, the chaos increase further. 😱

Photo by Ryan Snaadt on Unsplash

One of the best JavaScript feature is that the code is not compiled, and it can be executed immediately in a browser. This make the life easier for a developer and it is very easy to get immediate feedback on written code. Unfortunately, however, this simplicity in the last period has been undetermined by what I consider “The cult of excessive instruments”. Theese tools have turned JavaScript development into a real nightmare of complexity. There are entire online courses for Webpack configuration guide! Yes, you got it right…a whole course!

The chaos of the tools has increased to the point that many developers are eager to get back to actually writing code rather than playing with configuration files. An emerging project that aim to resolve this problem its Facebook’s Rome project.

Deno, on the other hand, has an entire and complete ecosystem, such as runtime and modules management. This approach offer to the developer all the tools they need to build their applications. Now, let’s take a look at the tools that Deno 1.6 ecosystem offer, and how the developers can use them to reduce third party dependencies and simplify the development.

It’s not possible to replace an entire build pipeline in Deno, but I think that we don’t think we’ll wait much longer before we have it. Below the list of integrated features:

bundler: it write in a single JavaScript file the specified module and all its dependencies;

it write in a single JavaScript file the specified module and all its dependencies; debugger: it give to us the ability to debug our Deno program with Chrome Devtools, VS Code and other tools;

it give to us the ability to debug our Deno program with Chrome Devtools, VS Code and other tools; dependency inspector: if we execute this tool on a ES module it show all the dependencies tree;

if we execute this tool on a ES module it show all the dependencies tree; doc generator: it analyze all the JSDoc annotation in a given file and produce the documentation for us;

it analyze all the JSDoc annotation in a given file and produce the documentation for us; formatter: it format the JavaScript, or TypeScript, code automatically;

it format the JavaScript, or TypeScript, code automatically; test runner: it’s an utility that give to us the ability to test our source code using the assertions module of the standard library.

it’s an utility that give to us the ability to test our source code using the module of the standard library. linter: useful for identifying potential bugs in our programs.

Bundler

Deno can create a simple bundle from command line using deno bundle command, but it expose an API internally. With this API the developer can create a custom output, or something that can be used for frontend purpose. This API is instable, so we need to use the --unstable flag. Let’s take the example we did earlier, modifying it as follows:

// file colors.ts

import { green } from "https://deno.land/std@0.65.0/fmt/colors.ts"; console.log(green("Hello Deno! 🦕"));

And now let’s create our bundle from command line:

$ deno bundle colors.ts colors.bundle.js

this command create a file colors.bundle.js that will contains all the source code that we need to execute it. In fact if we try to run the script with command:

$ deno run colors.bundle.js

we will notice that no module will be downloader from Deno’s repository, this because all the code needed for the execution is contained in the colors.bundle.js file. The result that we will see on the terminal is the same of the previous example:

Debugger

Deno has an integrated debugger. If we want to launch a program in debug mode manually we need to use the --inspect-brk

$ deno run -A — inspect-brk fileToDebug.ts

Now if we open Chrome inspector chrome://inspect we find a page similar to this

If we click on inspect we can start to debug our code.

Dependency inspector

Use this tool it’s really simple! Simply we need to use the info subcommand followed and URL (or path) of a module and it will print the dependency tree of that module. If we launch the command using the server.ts file used in the previous example, it will print in our terminal someting like this:

Also the command deno info can be used to show cache information:

Doc generator

This is a really useful utility that allows us to generate the JSDoc automatically. If we want to use it just run the command deno doc , followed by a list of one or more source files, and automatically it will printed to terminal all the documentation for all the exported files of our modules. Let’s take a look on how it works with a simple example. Let’s imagine tha we have a file add.ts with following content:



* Adds x and y.

*

*

*

*/

export function add(x: number, y: number): number {

return x + y;

} /*** Adds x and y. @param {number} x @param {number} y @returns {number} Sum of x and y*/export function add(x: number, y: number): number {return x + y;

Executing the deno doc command, it will printed the following JSDoc on the standard output:

It’s possible tu use the --json flag to produce a JSON format documentation. This JSON format can be used by Deno’s website to generate the module documentation automatically.

Formatter

Formatter is provided by dprint, an alternative to Prettier, that clone all the rules enstabished by Prettier 2.0. If we want to format one or more files, we can use deno ftm <files> or a VSCode extension. If we run the command with the --check flag, will be executed the format check of all JavaScript and TypeScript files in the current working directory.

Test runner

The syntax of this utility it’s really simple. We just need to use the deno test command and it will be executed the tests for all files that ends with _test or .test with the .js , .ts , .tsx or .jsx extensions. In addition to this utility it’s possible to use the standard Deno API that give to us the asserts module that we can use in the following way:

import { assertEquals } from "https://deno.land/std/testing/asserts.ts"



Deno.test({

name: "testing example",

fn(): void {

assertEquals("world", "world")

assertEquals({ hello: "world" }, { hello: "world" })

},

})

This module give to us nine assertions that we can use in our test cases:

assert(expr: unknown, msg = ""): asserts expr

assertEquals(actual: unknown, expected: unknown, msg?: string): void

assertNotEquals(actual: unknown, expected: unknown, msg?: string): void

assertStrictEquals(actual: unknown, expected: unknown, msg?: string): void

assertStringContains(actual: string, expected: string, msg?: string): void

assertArrayContains(actual: unknown[], expected: unknown[], msg?: string): void

assertMatch(actual: string, expected: RegExp, msg?: string): void

assertThrows(fn: () => void, ErrorClass?: Constructor, msgIncludes = "", msg?: string): Error

assertThrowsAsync(fn: () => Promise<void>, ErrorClass?: Constructor, msgIncludes = "", msg?: string): Promise<Error>

Linter

Deno has an integrated JavaScript and TypeScript linter. This is a new feature and it’s instable and, obviously, if we want to use it require the --unstable flag to execute it.

# This command lint all the ts and js files in the current working directory

$ deno lint --unstable # This command lint all the listed files

$ deno lint --unstable myfile1.ts myfile2.ts

Benchmark

Ok folks! We’ve arrived to the moment of truth! Who’s the best JavaScript enviroment Deno or Node? But I think that the correct question is another: Who’s the fastest? I did a really simple benchmark (an http hello server) and the results were very interesting. I made them on my laptop that has the following characteristics:

Model: XPS 13 9380

Processor: Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz

RAM: 16GB DDR3 2133MHz

OS: Ubuntu 20.04 LTS

Kernel version: 5.4.0-42

The tool that I used to make these benchmark is autocannon and the used scripts are the following:

// file node_http.js

const http = require("http"); const hostname = "127.0.0.1";

const port = 3000; http.createServer((req, res) => {

res.end("Hello World");

}).listen(port, hostname, () => {

console.log("node listening on:", port);

});



import { serve } from " // file deno_http.tsimport { serve } from " https://deno.land/std@0.61.0/http/server.ts "; const port = 3000;

const s = serve({ port });

const body = new TextEncoder().encode("Hello World"); console.log("deno_http listen on", port);

for await (const req of s) {

const res = {

body,

headers: new Headers(),

};

res.headers.set("Date", new Date().toUTCString());

res.headers.set("Connection", "keep-alive");

req.respond(res).catch(() => {});

}

We can find them in the following github repository:

The first test case was performed on 100 concurrent connections with the command autconannon http://localhost:3000 -c100 and the results are the sequent:

It seems that Node beat Deno in velocity! But this benchmark is based on 100 concurrent connections which are many for a small or medium server. So let’s do another test: this time with 10 concurrent connections.

And again, Node beats Deno:

Seems that, in term of performance, Node beats Deno 2–0! It performs better in both analyzed cases. However, the Deno it’s a really young project and the community is working hard to get adopted in the production environment soon, but it will be a tough fight again a titan like Node.

Conclusion

The main purpose of this article was not to support either Node or Deno, but rather to compare the two enviroments. Now you should have an understanding of the similarities and differences between the two.

Deno has some particular advantages for developers, including a robust suppport system and natively TypeScript support. The design decisions and additional built-in tools aim to provide a productive environment system and a good developer experience. I don’t know if these choices can be a double-edged sword in the future, but seems to attract more developers right now.

Node, on the other hand, has a robust ecosystem, ten years of development and releases behind it, an oceanic community and online courses that can help us on many threads or problems, an infinite list of frameworks (Fastify, Express, Hapi, Koa etc.), many books like “Node.js Design Patterns” or “Node Cookbook” that I consider the best books that talks about Node.js. For these, and many other reasons, I think that Node is the most secure choice to make for now. What can I say…

HAPPY CODING!

Bibliography