An Introduction to Deno

By

Since its introduction in 2009, Node.js has gained huge popularity and usage. But with that, issues with its ecosystem, feature adoption and dependency bloat have started to surface.

So, in true JavaScript community style, there's a new kid on the block: Deno 🦕

Image

What is Deno?

Deno is a new runtime for JavaScript and Typescript, built on Google's V8 engine and written in Rust. It was started by Ryan Dahl (who famously started Node.js) as an answer to the problems he saw with Node.js and its ecosystem.

Ryan announced the project a couple of years ago at JSConf EU during a talk in which he went into some detail about regrets he had over Node.js, particularly around decisions he did (or didn't) make along the way. It's definitely worth a watch.

Although seen as a Node.js successor, there are some major differences between the two:

  • Deno has no package manager.
  • Deno implements a security sandbox via permissions.
  • Deno has a standard library for common tasks.
  • Deno has first-class TypeScript support.
  • Deno will be able to be compiled into a single executable.

No package manager

Instead of the complex module resolution that Node.js supports, Deno simply uses URLs for dependencies and doesn't support package.json. Import a relative or absolute URL into your project, and it'll be cached for future runs:

import { listenAndServe } from "https://deno.land/std/http/server.ts";

Third party modules can be added to Deno's website via https://deno.land/x/.

Security

By default, a Deno application will not be able to access things like your network, environment or file system. Unlike Node.js, in order to give an application access to this sandboxed functionality you'll need to use one of the provided flags:

$ deno run server.ts --allow-write

You can see all of Deno's supported security flags by running deno run --help.

Standard library

Much like Go, the Deno team maintains a core, stable set of utilities in the form of a standard library. These cover utilities such as logging, http serving and more. If you need to implement a feature, it's probably best to check the standard library first to see if it's already supported.

You can see what's available in Deno's standard library via its source code.

TypeScript

Unlike Node.js, Deno has first-class support for TypeScript (most of its standard library is written in it). This means that ES modules and all the goodness of static typing are available right from the start, with no transpilation required on the user side. It's worth noting however that Deno still needs to compile TypeScript to JavaScript behind the scenes, and as such incurs a performance hit at compile time unless the module's already been compiled and cached.

If you'd rather not use TypeScript, Deno supports JavaScript files too.

Single executables

Although not implemented yet, one future ambition is to allow a Deno application to be compiled down into a single executable. This could vastly improve and simplify the distribution of JavaScript-based applications and their dependencies.

You can track the progress of single executable compilation on GitHub.

Running Deno

Now we know what Deno is, let's have a play with it.

The Deno website provides plenty of installation options, but since I'm using macOS I'll use Homebrew:

$ brew install deno

Once installed, deno should be available to use from your terminal. Run deno --help to verify the installation and see what commands it provides.

Deno also gives the ability to run applications with just a single source URL. Try running the following:

$ deno run https://deno.land/std/examples/welcome.ts

Download https://deno.land/std/examples/welcome.ts
Warning Implicitly using master branch https://deno.land/std/examples/welcome.ts
Compile https://deno.land/std/examples/welcome.ts
Welcome to Deno 🦕

Deno downloads the module from the provided URL, compiles it and runs the application. If you visit the above module's URL in your browser, you'll notice that Deno also provides a nice browser UI for the module's source code, which in this case is a simple console.log statement.

Of course running arbitrary third party code like this should always be treated with caution, but since it's an official Deno example we're all good here, and as mentioned above, Deno's security flags should help limit any potential damage.

You'll also notice that if you run the same command again, the welcome.ts module isn't redownloaded. This is because Deno caches modules when they're first requested, allowing you to continue work on your project in places with limited internet access.

If for any reason you want to reload any of your imports, you can force this by using the --reload flag:

$ deno run --reload https://deno.land/std/examples/welcome.ts

Building your first Deno app

To demonstrate a few of Deno's features, let's dive into a simple API example. Nothing too complicated, just a couple of endpoints. And in true Potato style, we'll use different types of spuds for our test data.

It's worth noting beforehand that this demo won't rely on any third party modules, and will use an in-memory data store. There are plenty of libraries (some are linked at the bottom of this article) that aim to make this simpler, but for now let's stick with vanilla Deno!

Setting up the server

Firstly, let's create a TypeScript file. Don't worry too much if you're not familiar with TypeScript, you can use plain JavaScript too. I'll create mine at server.ts.

Next, we need to set up a simple web server. As we've already seen, Deno has a standard library that contains some useful functions with one of these being the http module. Taking inspiration from Go, there's a helpful listenAndServe function that we can use:

import {
  listenAndServe,
  ServerRequest,
} from "https://deno.land/std/http/server.ts";

listenAndServe({ port: 8080 }, async (req: ServerRequest) => {
  req.respond({ status: 204 });
});

console.log("Listening on port 8080.");

What's happening here? Firstly, we import the listenAndServe method from Deno's http module, and the ServerRequest interface to allow TypeScript type checking. Then, we create a simple server that listens on port 8080 and responds to all requests with a HTTP 204 No Content response.

As mentioned above, by default Deno will prevent our application from accessing the network. To run this successfully, we'll need to use Deno's --allow-net flag:

$ deno run --allow-net server.ts

We can verify our application is running correctly using cURL in another terminal tab:

$ curl -i -X GET http://localhost:8080

HTTP/1.1 204 No Content
content-length: 0

Environment variables

To show how environment variables are passed to Deno, let's add support for a dynamic port number since this is a common use case amongst production servers. Deno provides the Deno.env runtime library to help with retrieving the current environment variables:

import {
  listenAndServe,
  ServerRequest,
} from "https://deno.land/std/http/server.ts";

const { PORT = "8080" } = Deno.env.toObject();

listenAndServe({ port: parseInt(PORT, 10) }, async (req: ServerRequest) => {
  req.respond({ status: 204 });
});

console.log(`Listening on port ${PORT}.`);

We can now pass a custom port to our application when running it. One thing to note here is that we need to convert the port variable to a number, since all environment variables are passed as strings and listenAndServe expects a number for the port.

When running this, we'll also need to use the --allow-env flag to grant the application access to our environment variables:

$ PORT=6060 deno run --allow-net --allow-env server.ts

Routes

For the sake of simplicity, we'll implement a very simple router ourselves using a good old fashioned switch statement.

Firstly, let's create some empty route handlers. We'll create two: one to allow a new spud type to be added to a list, and another for retrieving the current list. For now, let's return a HTTP 204 No Content response so that we can test our application along the way:

const createSpud = async (req: ServerRequest) => {
  req.respond({ status: 204 });
};

const getSpuds = (req: ServerRequest) => {
  req.respond({ status: 204 });
};

Next, let's create a handleRoutes method that'll act as our router:

const handleRoutes = (req: ServerRequest) => {
  if (req.url === "/spuds") {
    switch (req.method) {
      case "POST":
        createSpud(req);
        return;
      case "GET":
        getSpuds(req);
        return;
    }
  }

  req.respond({ status: 404 });
};

Here, we're checking every incoming request URL and method, and directing the request to the appropriate function. If neither the URL nor the method matches anything expected, we return a HTTP 404 Not Found to the user.

Finally, let's call the handleRoutes function from our original server and add a try statement around it to catch any errors and return an appropriate response:

listenAndServe({ port: parseInt(PORT, 10) }, async (req: ServerRequest) => {
  try {
    handleRoutes(req);
  } catch (error) {
    console.log(error);
    req.respond({ status: 500 });
  }
});

Using a try statement and catching errors in this way is usually a good idea with Deno, since unlike Node.js a Deno application will exit when it encounters an uncaught error.

We should now be able to send POST and GET requests to http://localhost:8080/spuds and get an expected HTTP response:

$ curl -i -X GET http://localhost:8080

HTTP/1.1 404 Not Found
content-length: 0

$ curl -i -X GET http://localhost:8080/spuds

HTTP/1.1 204 No Content
content-length: 0

$ curl -i -X POST http://localhost:8080/spuds

HTTP/1.1 204 No Content
content-length: 0

Create handler

Next, let's add an in-memory store for our spud types:

const spuds: Array<string> = [];

In order to process the incoming spud data, we'll need to be able to parse the request's JSON body. Deno doesn't have a built in way of doing this at the time of writing, so we'll use its TextDecoder class and parse the JSON ourselves:

const createSpud = async (req: ServerRequest) => {
  const decoder = new TextDecoder();
  const bodyContents = await Deno.readAll(req.body);
  const body = JSON.parse(decoder.decode(bodyContents));
};

What's happening here? Essentially, we're first using the Deno.readAll method to asynchronously read the contents of the request body (a Reader) as bytes. We then decode that into a UTF-8 string, and finally parse it as JSON. Phew.

We can then proceed to add the spud type to the store we created earlier, and return a HTTP 201 Created response. Our final create handler should look something like this:

const createSpud = async (req: ServerRequest) => {
  const decoder = new TextDecoder();
  const bodyContents = await Deno.readAll(req.body);
  const body = JSON.parse(decoder.decode(bodyContents));

  spuds.push(body.type);

  req.respond({
    status: 201,
  });
};

Get handler

To implement our GET handler, we'll essentially reverse the operation we wrote above by using Deno's TextEncoder. We'll then set the relevant header to "application/json" using Deno's Headers class and return the spud data with a HTTP 200 OK response:

const getSpuds = (req: ServerRequest) => {
  const encoder = new TextEncoder();
  const body = encoder.encode(JSON.stringify({ spuds }));

  req.respond({
    body,
    headers: new Headers({
      "content-type": "application/json",
    }),
    status: 200,
  });
};

Final application

Our final file should look a bit like this:

import {
  listenAndServe,
  ServerRequest,
} from "https://deno.land/std/http/server.ts";

const { PORT = "8080" } = Deno.env.toObject();

const spuds: Array<string> = [];

const createSpud = async (req: ServerRequest) => {
  const decoder = new TextDecoder();
  const bodyContents = await Deno.readAll(req.body);
  const body = JSON.parse(decoder.decode(bodyContents));

  spuds.push(body.type);

  req.respond({
    status: 201,
  });
};

const getSpuds = (req: ServerRequest) => {
  const encoder = new TextEncoder();
  const body = encoder.encode(JSON.stringify({ spuds }));

  req.respond({
    body,
    headers: new Headers({
      "content-type": "application/json",
    }),
    status: 200,
  });
};

const handleRoutes = (req: ServerRequest) => {
  if (req.url === "/spuds") {
    switch (req.method) {
      case "POST":
        createSpud(req);
        return;
      case "GET":
        getSpuds(req);
        return;
    }
  }

  req.respond({ status: 404 });
};

listenAndServe({ port: parseInt(PORT, 10) }, async (req: ServerRequest) => {
  try {
    handleRoutes(req);
  } catch (error) {
    console.log(error);
    req.respond({ status: 500 });
  }
});

console.log(`Listening on port ${PORT}.`);

Let’s give this a test:

$ curl -i --data '{"type": "maris piper"}' -X POST http://localhost:8080/spuds            

HTTP/1.1 201 Created
content-length: 0

$ curl -i --data '{"type": "king edward"}' -X POST http://localhost:8080/spuds            

HTTP/1.1 201 Created
content-length: 0

$ curl -i -X GET http://localhost:8080/spuds                            

HTTP/1.1 200 OK
content-length: 54
content-type: application/json
{"spuds":["maris piper", "king edward"]}

If you'd rather, you can view this file as a Gist or run it directly with the following command:

$ deno run --allow-net --allow-env https://gist.githubusercontent.com/dcgauld/205218530e8befe4dfc20ade54e7cc84/raw/9eff7733cf017f33b2bf3144937f97702ae4fc63/server.ts

We just created our first Deno application!

Conclusion

Hopefully this article has given you a glimpse into the world of Deno, and some inspiration to start using it for future projects. I'm excited to see what the future holds for the project, especially around things like single file executables and the potential to run certain Deno modules in the browser.

If you'd like to learn more about it and its features, I'd really recommend giving the Deno manual a read.

Useful links

We created our first Deno API with no third party modules, but there are many libraries out there already that aim to simplify that process. Some examples:

Parcel and Rust: A WASM Romcom

By

Web Assembly (WASM) and Rust have been growing and evolving over the last couple of years, so what’s it like to use them together?

Introduction

I’ve wanted to use Rust and WASM for a while due to a number of reasons: small bundle size, low-level access with reliable performance, and all the perks that come with Rust (strong type safety, zero-cost abstractions, etc.). So, when I was presented with the opportunity of 2 weeks off-project learning, there was no excuse not to give Rust and WASM a try! What followed over the next 2 weeks or so was a bit of a programming rollercoaster for me, something all programmers have been through many times. But when it came time to write my experiences up for this article, I noticed there was a pattern, this experience wasn’t just any rollercoaster…it mapped perfectly to the structure of a Romcom! So, to explain and analyse this not officially supported pairing of a web application bundler and a systems programming language, we will be following the standard 10 part Romcom format, for structure and a bit of comedic relief.

Part 1: “An Unfulfilled Desire”

Another reason I wanted to use Rust and WASM is because it was new and shiny, plus it would be nice to hook up the Rust program to a nice web interface. One problem, Rust and WASM is only officially supported with Webpack as a bundler. To me, Webpack was that Ex in a Romcom, it was never a good relationship and we could never make it work. But, it seemed that it may be a necessary evil to reach my goal of making something using my lost love, Rust.

Part 2: “Meet cute”

So, I grudgingly start to clone the Rust WASM Webpack template as I’m flashed back to a previous project, watching myself as I battle with Webpack trying to compile a Single Page App (SPA). The list of dependencies growing with every plugin. I spam Ctrl + C, “No there must be something else” I think. And that's when it hits me, “Parcel! I remember it saying something about WASM?” I think as I quickly navigate to the Parcel website, and there it is, this is what I have been looking for, and after a quick npm install, I’m head over heels.

Part 3: “Happy Together”

One npm init and npm install -D parcel-bundler later and we are off to the races. Parcel supports importing .rs files in JS and TS out of the box so in a simple HTML5 boilerplate with a main.js I do just that. The rust file contains a simple function which when given 2 numbers returns their sum, some extra Rust to tell the compiler not to mangle the function name and it’s done! The JS calls this function and displays the output in the DOM, a simple example but this seems to have everything I need…

Part 4: “Obstacles Arise”

But, as with most romcoms, a bump in the road pulls the relationship into question. For Rust and Parcel, this issue was returning or accepting strings in functions. No matter what I did, it wouldn’t work and streams of undefined would plague my console. Although there seemed to be a solution, the well supported “wasm_bindgen” package provides a polyfill for things missing between Rust and JS! So, make a Rust project with a cargo.toml, add the wasm_bingen crate, import and run… oh wait. Parcel doesn’t seem to work with wasm_bindgen even with a plugin someone on a GitHub issue cites as the solution...what now?

Part 5: “The Journey”

After a few minutes of frantic Googling and skim reading GitHub issues and various tutorials on blogs I find an alternative package, stdweb. Seems to have most of the functionality of wasm_bindgen and a handy tutorial with how to set it up with Parcel! A quick switcheroo of the packages in the cargo.toml, some slight code tweaks and we are back on course with strings being returned and received in this simple app. Time to start making something slightly more complex...a simple genetic algorithm simulator! Image

Part 6: “New Obstacles”

Okay so new project, Parcel - installed, Rust module - created, stdweb - installed lets get this show on the road! In my head the idea is simple, make a struct in Rust to represent the Genetic Algorithm Simulation, add some methods to it to get the population or simulate a generation, and then simply instantiate and use it in JS. Can’t be too hard surely (foreshadowing)! Lets just make the struct, seems to be instantiating in JS, lets add some methods onto the struct… wait...what? It seems exporting structs is temperamental at best when using stdweb and parcel am I back to square 1 already?

Part 7: “The Choice”

All seems lost, I’m out of viable Rust packages to try and have a console littered with errors, is there nothing I can do? In a last ditch effort I tried manually compiling the .wasm file myself and importing it but after 5 edits to the Rust file I can already feel this getting tedious… As I crawl through GitHub issue after GitHub issue webpack comes up again and again as the solution, maybe I need to accept defeat and go back.

Part 8: “Crisis”

F@$% I’m going to have to use Webpack, I think as I go back to the start and open the Webpack Rust template, defeated.

Part 9: “Epiphany”

As the Webpack Rust template repo clones I took to Google one last time, using one of my old searches, hoping for a miracle. Wait, what's this? A GitHub issue about Parcel and WASM_Bindgen which wasn’t there before? The Google search index must have only just found this to be relevant? Hold on, someone has linked a template here for Rust, WASM_Bindgen, and Parcel! Thank the Search Engine Gods the project may be saved!

Part 10: “Resolution”

There it was, under my nose the whole time on the rustwasm GitHub repository. I quickly cloned the repo and followed the set-up instructions and it all worked flawlessly. In the end this search had become a real Cinderella story with the perfect match being found on the stroke of midnight. So now, time to make something cool with it! I didn’t want to focus too much on the front end and slaving over SCSS making it look nice, so I turned to an old friend: TailWindCSS, a utility-first CSS framework which I have set up with PostCSS and Parcel before. With all that done I build out a simple layout with a side panel for configuring the simulation and a main panel to hold the results of the simulation. After deciding on the look and feel of the page I start to make some TypeScript components for controlling and displaying the simulation. Finally after getting the site up and running with some mock data from a simple set_interval I start to hook it up to the WASM. It ends up being remarkably simple, just import the module object from the Rust projects’ cargo.toml and then all the structs and functions are attached to it! A few little tweaks and testing and what do you know, it's all working and converging! A little bit of cleanup and then I deploy it on Firebase and it’s hosted happily ever after. Image

Conclusion

Now this article has been a bit of fun writing in this style and talking about a project I’ve genuinely enjoyed every minute of, and every up and down. But, what is it actually like using Rust and Parcel? I can wholeheartedly say it is a true pleasure...once you find the right resources. Parcel just makes it so easy with no configuration needed for most projects, and although it might not be as fast on larger projects it will give the big dogs a run for their money 9/10 times! As for Rust and WASM it was everything I expected and more. Rust has always been a language I have loved programming in and, although it's a challenge, it never gets old. However, if I am to complain about one thing about this experience, it would be the lack of intellisense on the exported JS module. It may not be an issue when you write the tiny Rust file being compiled but I can see this being painful on larger projects using Rust, WASM, and Parcel. In conclusion, if you have ever had a little voice telling you to give Rust or WASM a go, I would highly recommend it and maybe consider using Parcel to avoid the emotional rollercoaster I went on to get it done!