Thanks! We'll be in touch in the next 12 hours
Oops! Something went wrong while submitting the form.

Node.js vs Deno: Is Deno Really The Node.js Alternative We All Didn’t Know We Needed?

Ryan Dahl gave an interesting talk at JSConf EU in 2018 on the 10 regrets he had after creating Node.js. He spoke about the flaws that developers don’t usually think about, such as how the entire package management was an afterthought. In addition, he was also not completely comfortable with the association of npm for package management—or how he might have jumped early to async/await, ignoring some potential advantages of promises

However, the thing that caught most people’s attention was his pet project (Deno), which he started to answer most of these issues. The project came as no surprise since no one discusses problems at length unless they are planning to solve them.

What first appeared to be a clever play on the word “node” turned out to be much more than that. Dahl was trying to make a secure V8 runtime with TypeScript and module management that was more in-line with what we already have on the front-end. This goal was his original focus—but fast-forward to May 2020 and, Deno 1.0 was launching with many improvements. Let’s see what it is all about.

What is Deno?

Let’s start with a simple explanation: Deno executes TypeScript on your system like Node.js executes JavaScript. Just like Node.js, you use it to code async desktop apps and servers. The first visible difference is that you will be coding in TypeScript. However, if you can easily integrate the TypeScript compiler into Node.js for static type-checking, then why should you use Deno? First of all, Deno isn't just a combination of Node.js and TypeScript—it’s an entirely new system designed from scratch. Below is a high-level comparison of Node.js and Deno. Bear in mind that these are just paper specs.

Nodejs vs Deno

As you can see, on paper, Deno seems promising and future-proof, but let’s take a more in-depth look.

Let’s Install Deno

As Deno is new, it intends to avoid a lot of things that add complexity to the Node ecosystem. You just need to run the following command to install; these examples were done in Linux:

CODE: https://gist.github.com/velotiotech/7fb2cc56989d3416761ffcea987c106b.js

This simple shell script downloads the Deno binary to a .deno directory in your home directory. That’s all. It’s a single binary without any dependencies, and it includes the V8 JavaScript engine, the TypeScript compiler, Rust binding crates, etc. After that, add the ‘deno’ binary to your PATH variable using:

CODE: https://gist.github.com/velotiotech/271c1f0bb6dd2b6e42d1120b4ea8274c.js

Let’s Play

Now that we’ve finished our installation, we can start executing TypeScript with it. Just use ‘deno run’ to execute a script.

So, here is the “hello world” program as per the tradition:

CODE: https://gist.github.com/velotiotech/8e9fb84efef31696ba7c1500ba4dedb2.js

It can also run a script from an URL. For instance, there is a "hello world" example in the standard library hosted here. You can directly run it by entering:

CODE: https://gist.github.com/velotiotech/3ddc2264d42e08167020e0448e233318.js

Similar to an HTML’s script tag, Deno can fetch and execute scripts from anywhere. You can do it in the code, too. There is no concept of a "module" like there is with npm modules. Deno just needs a URL that points to a valid TypeScript file, and it will run/import it. You will also see an import statement like:

CODE: https://gist.github.com/velotiotech/0526119aec2ca2f09db35706841e2115.js

This may seem counterintuitive and chaotic at first, but it makes a lot of sense. Since libraries/modules are just TypeScript files over the Internet, you don’t need a module system like npm to handle your dependencies. You don't need a package.json file, either. Your project will not suddenly blow up if something goes wrong with npm’s registry.

Going Further

Let’s do something more meaningful. Here is a basic server using the HTTP server available with the standard library.

CODE: https://gist.github.com/velotiotech/a5022a1763ed5985c5e5692dc7f12d95.js

Take notice of the URL import that we were talking about. Next, make a file named server.ts, input this code, and try to run it with:

CODE: https://gist.github.com/velotiotech/dab118ce19cc6e2c5eaf9bdda3a73627.js

We get this:

CODE: https://gist.github.com/velotiotech/f5115842f0afce2c19f211fc56b79f2f.js

This seems like a permission issue, which we should be able to solve by doing 'sudo', right? Well, not exactly. As it says, you will have to run it with a '--allow-net' flag to manually permit it to access the network. Let’s try again with:

CODE: https://gist.github.com/velotiotech/a0243f85d9edeed6651e588f99b70112.js

Now it runs. So, what’s happening here? 

Security

This is another aspect that is completely missing in Node.js. Deno, by default, does not allow access to system resources like network, disk, etc. for any script. You have to explicitly give it permission for these resources which adds a layer of security and consent.

If you are using a lesser-known library from a small developer, which we often do, you can always limit its scope to ensure that nothing shady is happening in the background. This also complements the “free and distributed” nature of Deno when it comes to adding dependencies. As there is no centralized authority to watch over and audit all the modules, everyone needs to have their own security tools.

There are also different flags available, which provides granular control over a system's resources, like "--allow-env" for accessing the environment. But if you trust the script entirely, or it is something you have written from scratch, you can use the '-A' flag to give it to access all the resources.

Plugins and Experimental Features

Let’s look at an example of interacting with a database. Connecting a MongoDB instance with Deno is another common thing that developers usually do.

For a MongoDB instance, let’s spin up a basic MongoDB container with:

CODE: https://gist.github.com/velotiotech/5751d795ad65350963ec162a0c95d49b.js

Now, when we make a file named database.ts and put the following code into it, it will create a simple document into a new collection named "cities":

CODE: https://gist.github.com/velotiotech/6e0428333c15b3ff2efb6a6dde760103.js

Now, as you can see, this looks pretty similar to Node.js code. In fact, most of the programming style remains the same, and you can follow similar patterns. Next, let’s run this to insert that document into our MongoDB container:

CODE: https://gist.github.com/velotiotech/9a9d2411540e7c5df2904cabcf76b2b4.js

What happens is that you get an error that looks something like this:

CODE: https://gist.github.com/velotiotech/f3f3c9613446dc602ec335566199d2f1.js

This happened becuase we even gave the '-A' flag which allowed access to all kinds of resources.

Let’s rerun this with the '--unstable' flag:

CODE: https://gist.github.com/velotiotech/9db648e6bead8adddccef84018d12c2f.js

Now, this seems to run, and the output should look something like:

CODE: https://gist.github.com/velotiotech/55aa80d8c63fe10b417f58507ee7a6ae.js

This happens because the MongoDB driver uses some extra capabilities ("ops" to be precise). They are not present in the Deno’s runtime, so it adds a plugin. While Deno has a plugin system, the interface itself is not finalized and is hidden behind the '--unstable' flag. By default, Deno doesn’t allow scripts to use unstable APIs, but again, there is this flag to force it.

The Bigger Picture

Why do we need a different take? Are the problems with Node so big that we need a new system? Well, no. Many people won’t even consider them to be problems, but there is a central idea behind Deno that makes its existence reasonable and design choices understandable:

Node deviates significantly from the browser’s way of doing things.

For example, take the permissions for when a website wants to record audio; the browser will ask the user to give consent. These kinds of permissions were absent from Node, but Deno brings them back.

Also, regarding dependencies, a browser doesn’t understand a Node module; it just understands scripts that can be linked from anywhere around the web. Node.js is different. You have to make and publish a module so that it can be imported and reused globally. Take the fetch API, for example. To use fetch in Node, there is a different node-fetch module. Deno goes back to simple scripts and tries its best to do things similar to a browser.

This is the overall theme even with the implementational details. Deno tries to be as close to the browser as possible so that there is minimal friction while porting libraries from front-end to back-end or vice-versa. This can be better in the long term.

This All Looks Great, but I Have Several Questions

Like every new take on an already-established system, Deno also raises several questions. Here are some answers to some common ones:

If it runs TypeScript natively, then what about the speed? Node is fast because of V8.

The important question is whether Deno actually ‘run’ TypeScript.

Well, yes, but actually no.

Deno executes TypeScript, but it also uses V8 to run the code. All the type checks are done before then, and at the runtime, it's only JavaScript. Everything is abstracted from the developer’s side, and you don’t have to install and configure tsc.

So yes, it’s fast because it also runs on V8, and there are no runtime types.

The URL imports look ugly and fragile. What happens when the website of one of the dependencies goes down?

The first thing is that Deno downloads and caches every dependency, and they recommend checking these with your project so that they are always available. 

And if you don’t want to see URL imports in your code, you can do two things:

1. Re-export the dependencies locally: To export the standard HTTP server locally, you can make a file named 'local_http.ts' with the following line 'export { serve } from "https://deno.land/std@0.57.0/http/server.ts"' and then import from this file in the original code.

2. Use an import map: Create a JSON file that maps the URLs to the name you want to use in code. So, create a file named 'importmap.json' and add the following content to it:

CODE: https://gist.github.com/velotiotech/2cd6bd994e22f5ac97a2f06a311ef8ae.js

Now, you just need to provide this as the importmap to use when you run the script:

CODE: https://gist.github.com/velotiotech/c2b83bf64871efd010a9bfaf23c7ab3c.js

And you can import the serve function from the HTTP name like:

CODE: https://gist.github.com/velotiotech/907dfb82bcdecb3de7828806fdf6358c.js

Is it safe to rely on the URLs for versioning? What happens if the developer pushes the latest build on the same URL and not a new one?

Well, then it's the developer's fault, and this can also happen with the npm module system. But if you are still unsure whether you have cached the latest dependencies, then there is an option to reload some or all of them. 

Conclusion

Deno is an interesting project, to say the least. We only have the first stable version, and it has a long way to go. For instance, they are actively working on improving the performance of the TypeScript compiler. Also, there are a good number of APIs hidden behind the '--unstable' flag. These may change in the upcoming releases. The ideas like TypeScript first and browser-compatible modules are certainly appealing, which makes Deno worth keeping an eye on.