All Articles

On Deno and the future of Node

What would Node look like if it was written today? In one word: Deno. The JS runtime has Typescript build in and simplifies module resolution. On top, it takes security to the next level and closes the gap between how we write javascript on the backend and the browser.

Not so long ago …

Released in 2009 node took over the world incredibly fast. Despite the initial skepticism about running javascript in the backend, the community backup was unrivaled. Soon, sophisticated tooling appeared, and years later(2014), Microsoft released Typescript, double betting on Javascript.

Today, Node is one of the most popular choices for backend development. The event-based server philosophy ensures a high performance/throughput ratio. Running Javascript makes is an accessible tool for many developers. In a way, one can say, Node democratized backend development by lowering the barrier of entry. I have been happily using Node in the last five years, but at the same time, I wonder what does the future awaits?

The kid around the block: Deno

Started in 2018, the Deno project, as the website states, provides a secure runtime for Javascript and Typescript. It is composed of basically two parts: a typescript frontend and a Rust backend. The communication between the two happens by messaging with typed Arrays.

68747470733a2f2f64656e6f6c69622e6769746875622e696f2f686967682d7265732d64656e6f2d6c6f676f2f64656e6f5f68722e706e67

Deno is a JavaScript/TypeScript runtime with secure defaults and a great developer experience. — The Deno Website

Under the hood, we find a snapshotted version of the Typescript compiler, the V8 engine, and the Tokio event loop. Altogether, shipped as one binary of less than ten MB or as a Rust crate.

Ageing API’s

Removing promises from Node back in 2010, helped the community at is early stage. But as javascript started to move faster and faster and introducing the await and async functionalities, Node’s APIs started to age.

A considerable effort is made today to bring them up to speed and keep consistent versioning at the same time. Many of the API calls must still be wrapped in constructors like promisify to be used with the Promise syntax. This extra step adds overhead to development and increases boilerplate in applications.

In contrast, Promises are Deno’s native bindings for async behavior. The Rust backend mirrors the promise objects received from the Typescript frontend with Rust Futures. Async actions in Deno always return a Promise.

Another noticeable thing about Node is that it relies on Buffers to read and write data. In a step to bring uniformity with browser interfaces, Deno uses TypedArrays everywhere. Being consistent when reading and writing files across the backend and front end is much easier when using the same data structures.

Typescript with Zero Setup

If you use Typescript, you know it is a remarkable tool. It introduces a type system that can be enforced as applications grow. This reduces the overhead of conventional static typing by providing flexibility. A project can be partially typed in the begging, and type coverage can be extended as the application grows.

In Node, Typescript can be used directly with node-ts, although one must be careful in production. The safest and most performant choice is to use node-ts for development. Then compile to javascript for production. The setup for development can be complicated, especially together with other features like hot code reloading.

On the other hand, Deno is all about Typescript. It uses a snapshotted version of the compiler and catches unchanged files. Do you want to run Typescript code? Just run the Deno binary. No config. No hustle. Is that easy, and of course it supports javascript too.

Browser Like Package Resolution

The current resolution scheme of Node overcomplicates module resolution. The algorithm provides flexibility in file location and naming with a considerable tradeoff in complexity.

A require call would first search for a file with the same name and a .js, .json, or .node extension. If the path specified does not include a leading '/', './', or '../' node assumes the module is a core module or a dependency in the node_modules folder. If the name does not match, a core module node will check the node_modules at that location. If nothing is found, it will get to the parent directory and continue to do so until it reaches the root of the file system.

Additionally, folders can be specified as modules in the package.json file. The require function is also aware of the package.json file of all the folder begins checked. Once a folder is found, Node will look for anindex.js or index.node file inside it. The freedom of not having to provide a file extension and the flexibility of package.json comes at a considerable increase in complexity and decrease in performance.

Deno simplifies the algorithm by providing two types of module resolution: relative and URL based. In addition, the resolution algorithm does not use package.json file or the node_modules folder. Instead of require, it uses ES Modules imports. This allows us to use a modern approach to code management without the need of a pre-compiler and brings us again closer to how Javascript is used in the browser.

Distributed Package Management

Server-less adoption is at this moment doubling every year. Developers use to split monoliths into microservices. Now we are splitting micro-services into functions. Why? Well, on the one hand, nobody wants to deal with orchestration unless we have too. On the other hand, distributed systems are more flexible and can be changed faster. The bottom line is, applications are becoming systems of smaller and separated parts.

A typical javascript backend application represents 0.3% of the code is using. The rest is made up of packages in the node_modules folder. And many are hardly used at runtime. At the same time, the whole ecosystem depends on a centralized package manager: npm.

Deno brings a distributed approach to package management. Packages can be resolved by URL and catched afterwards. Applications are lighter and less dependent on a single and centralized package registry.

On Security

When doing backend development, I expect security to work outside the box. The last thing I want to think about is a linter file or node module accessing the network or the file system.

In Deno, internal functions cannot call V8 API’s arbitrarily as they do in Node. The communication between Deno’s APIs and the JS engine is centralized and unified with messaging based on typed arrays

Unless specifically allowed, scripts can’t access files, the environment, or the network. — deno.land

Scripts executed with Deno can access the file system and network only if the user explicitly specifies it. And even better, permission can be given at file, folder level, or network path level with the —allow flag. This offers developers granular control of read and write actions that happen at runtime.

Security by default is a significant upgrade compared to the “trust” policy applied to dependencies one pulls from npn. With Deno you can run and develop applications with the confidence that they will do what they are expected to.

Summing up

Deno is how Node would look like if it would be built today. It improves security, simplifies module resolution, and runs Typescript. As I am writing this article, we are still at version 0.33 and growing fast. I am sure if you are here is because you are to some degree using Node or Javascript. If you are like me, you probably love it. But as they say, to love something truly means to let it go.

I am looking forward to seeing Deno grow beyond merely a scripting runtime and to hearing about the first experiences in production. As long as developers continue disrupting ourselves, we can always expect faster, more straightforward, and more reliable software.