Mahdi Pourismaiel Articles

What's up with Deno?

What's up with Deno?

Deno 2 was released a couple of weeks ago and most of us have seen the fun teasers and have heard of the new features. But why should it matter? Is it any better than Node.js? What does it mean for the JavaScript ecosystem?

Node issues

Node.js has been around for a while now and it has been the go-to backend JavaScript runtime for many developers. But it has its issues. The most important issue is, believe it or not, the popularity which attracts bad actors like hungry bears to the river. The npm ecosystem is huge with millions of packages that are downloaded billions of times each week and let's be honest, we have no idea what's in them.

The npm ecosystem provides various packages and most of them are developed by a single person or a small team with no outside support, usually not receiving any technical help let alone any monetary support which understandably causes burnout especially when the package becomes popular. This leads to the package being abandoned or worse, being passed on to a malicious actor.

The ideal scenario would be that we as the community would take over and our companies would support the development of these packages that we so easily install and speed up our development. But that's not happening. What's happening is a package that was responsible for checking if a number was even is suddenly sending requests to a remote server. With Node.js there's no way you can catch this unless you do a bunch of stuff in a wrapper. With Deno, this is not the case.

Deno philosophy

What I described in the previous section is the most important issue with Node.js runtime but there are a lot more issues that came up over the years. Such as setting up typescript, the lack of a standard module system, the lack of a standard testing framework, the lack of standard formatting, linter, becnhmarking and more. Deno solves a lot of these issues. One might think these issues can be solved with installing a bunch of packages and that just emboldens the security issue I just mentioned and Deno solves that to a great degree as well.

1mkdir my-project
2cd my-project
3npm init -y
4npm i typescript tsx eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin prettier eslint-config-prettier eslint-plugin-prettier
5# set up the files .eslintrc.json, .prettierrc.json, tsconfig.json
6# hopefully everything works the way you want it to, if not, good luck!

Deno provides a permission system that allows you to control what your code can do. Upon starting a Deno script, you can pass in permissions to access filesystem, network, environment variables and subprocesses. This ensures that you can safely run code that you don't trust and that includes packages you installed from npm (which Deno 2 now supports out of the box). You can check out Deno's security documentation for more information on the related options. With this system in place, you can be sure that the code you're running is not doing anything you are not expecting it to do. That still doesn't prevent packages from acting malicious but with proper structuring of your code (separating remote contacting code from the rest of your code for example) you can have nicer weekends!

1deno run --allow-net --allow-read --allow-env server.ts

Typescript, testing, linter and formatter support

With Node.js you had to run npm init to have a package.json which would host your dependencies and scripts. Then you had to add a tsconfig.json, prettier.config.json, eslint.config.json just to start coding. With Deno, you just run deno init and that's it. Deno would run your code whether it's TypeScript or JavaScript and with deno lint and deno fmt you can lint and format your code. No need to install or configure any packages. Also with deno test any file with suffix _test.js or _test.ts are treated as tests which have pretty good support for assertions, mocking, parallel testing and same permission system as the runtime (read more here).

1deno init my-project
2cd my-project
3deno lint # or deno lint src/ for a specific directory
4deno fmt # deno fmt src/ for a specific directory
5# Happy life!

It's important that Deno provides these features out of the box since your project would be much easier to manage and all of it is standard as in supported by the runtime itself, not community accepted standards which would mean it changes with a new trend. When you hire a developer familiar with Deno you can safely assume they are comfortable with the coding style which is one less thing to worry about and something that Go developers have been enjoying for a long while.

Deno server

It might seem a small thing, it did to me, but a good built-in server is really nice. Don't get me wrong, I love express and fastify but I'm tired of installing them with each test project I want to do and I'm hardly the only one, since Deno and Bun both went ahead and implemented one. I mean, these are server side runtimes, why the hell doesn't Node.js provide it. That's not to say I don't appreciate Node.js but come on! Deno's server is pretty simple to use and it's pretty powerful, it supports websockets, http2, https, and it's pretty fast. The parameters and return types are standard web APIs which is so nice to see. It has good documentation that I highly recommend you check out.

HTTP Server

1Deno.serve({ port: 4242, hostname: "0.0.0.0" }, async (req: Request) => {
2  console.log("Method:", req.method);
3
4  const url = new URL(req.url);
5  console.log("Path:", url.pathname);
6  console.log("Query parameters:", url.searchParams);
7
8  console.log("Headers:", req.headers);
9
10  if (req.body) {
11    const body = await req.text();
12    console.log("Body:", body);
13  }
14
15  return new Response("Hello, World!");
16});

Websockets

1Deno.serve((req) => {
2  if (req.headers.get("upgrade") != "websocket") {
3    return new Response(null, { status: 501 });
4  }
5
6  const { socket, response } = Deno.upgradeWebSocket(req);
7  socket.addEventListener("open", () => {
8    console.log("a client connected!");
9  });
10
11  socket.addEventListener("message", (event) => {
12    if (event.data === "ping") {
13      socket.send("pong");
14    }
15  });
16
17  return response;
18});

Other goodies

With Deno you get a lot more goodies than what I mentioned in brief detail above. You get a semantically versioned standard library (that you can note in your deno.json file), you have a great experience with monorepos and you get support for running Next, Astro, Vite and more and you get an all-in-one configuration file that you can use to configure your runtime.

Is it Node.js compatible? Kind of. You still have to migrate a bunch of stuff such as standard library and npm package names are prefixed, packages that don't have types included need an extra comment directive, node_modules is not really a thing in Deno and a bunch of other stuff. You should check out the migration guide before making any decisions.

Is it time to switch from Node.js? I'm still not sure. I waited a couple of weeks before even testing Deno 2 since I wanted to read other people's experiences and have a better overview of the issues. While I still haven't found anything that would convince me not to switch I'm still hesitant. I would say for a new side project, it's better to use Deno but for a production project of any size, you might want to branch out and test it a bunch first.

Is it better than Bun? Hell yeah it is! Bun is a great alternative if I want to run something faster than it does on Node.js but Deno is a much better alternative due to it's simple project structure and typescript support out of the box (I'm seriously sick of setting that up myself). And since speed isn't really a concern in most projects, I'd rather write the entire project with Deno in mind and write a package for Bun to run containing the speed critical part.

And with all that, I encourage you to give Deno a go and let me know what you think. I'm still trying to decide what to do with a bunch of different projects and I'd love to hear your thoughts on the matter.

© 2024 • Mahdi Pourismaiel •