Deno: A Simple Guide
Tags: javascript, typescript, deno
Published: 2020-05-23 09:22:59

---
title: Deno: A Simple Guide
published: true
description: Learn about deno, the highly anticipated new secure JavaScript runtime environment.
tags: javascript, typescript, deno
---

On the 13th of May 2020, a very promising step forward was taken in the JavaScript community. The first stable release of Deno, which has been in development for over 2 years, was released. Deno has been touted as a fresh approach to some of the mistakes and issues that occurred in the original development of NodeJS back in 2009. 

This blog post aims to cut through a lot of the hype you may see around Deno in an attempt to present the facts - so even a complete beginner to the JavaScript or NodeJS ecosystem can understand Deno and its impact.

## What is Deno?

Deno is a JavaScript runtime created by [Ryan Dahl](https://en.wikipedia.org/wiki/Ryan_Dahl), the original creator of NodeJS. It was originally announced by Ryan at [JSConf EU 2018](https://www.youtube.com/watch?v=M3BM9TB-8yA), born out of a desire by Dahl to improve and rethink certain aspects of the JavaScript runtime he originally created.

On the [deno website](https://deno.land/), the tagline reads:

> Deno is a simple, modern and secure runtime for JavaScript and TypeScript that uses V8 and is built in Rust.

What does this *actually* mean? Let's break this down, and clear up some of the jargon.

A JavaScript **runtime** is an environment which contains everything you need to execute a program written in JavaScript. In the case of NodeJS, the runtime is made up of 2 high level concepts:

* The V8 **JavaScript engine**. A JavaScript engine compiles and executes your JavaScript code. **V8** is a specific JavaScript engine written in C++ created and maintained by Google and used to power NodeJS, Chrome and Chromium.
* APIs and modules. These include modules for interacting with the file system, making HTTP requests, timers etc.

So with this in mind, we are in a much better position to understand Deno and how it is different to Node. 

Deno is a runtime that still uses the V8 engine to run JavaScript code. Deno differs because it's written in rust rather than C++. This includes the CLI and all the nice tooling that Deno provides, which come packaged up in a single, executable file. The rust code interfaces with the V8 engine through the [rusty_v8](https://github.com/denoland/rusty_v8) rust package (or crate, as they are known in the rust ecosystem). The rust crate [Tokio](https://tokio.rs) is used heavily in deno under the hood for asynchronous behaviour and scheduling. 

The modules and APIs in Deno are written in [TypeScript](https://www.typescriptlang.org) which is a first class citizen of the runtime. TypeScript code is compiled into JavaScript internally in Deno. This allows developers to write TypeScript and have it compiled and run as if it was just JavaScript, without installing any additional tooling.

## Why Deno?

So we now know how the actual runtimes differ. But *why*? What was wrong with the current one in Node? Why did Ryan Dahl and the rest of the deno team spend 2 years developing this? [Ryan Dahl himself goes into detail about this here](https://www.youtube.com/watch?v=M3BM9TB-8yA). He details the regrets he had about building Node in the first place, which laid the groundwork for deno. Ryan announces Deno in this talk, albeit in it's very early stages.

If you don't want to watch the video, or haven't got time. I respect that. Let's summarise the points.

### Regret: Not Sticking with Promises

NodeJS is built on the concept of callbacks. Callbacks are functions that are run when a task has finished executing. For example, when you read a file using the `fs` module:

```javascript
const fs = require("fs");

fs.readFile("index.html", function(err, data) {
	if (err) {
		console.log("Error Reading File!", err);
	}
	console.log(data);
});
```

The function passed as the second argument to `fs.readFile` here is the **callback**. This function will be run whenever node has finished reading your file and will return either the data (if successful) or an error (if something went wrong). Callbacks have since been eclipsed by [promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), which provide a simpler and more powerful API for dealing with asynchronous behaviour in JavaScript. 

Later, it became possible to convert functions with callbacks to use promises using the `promisify` function on the `util` module in Node. Here's the promisified version of the above example.

```javascript
const util = require("util");
const fs = require("fs");

const readFilePromise = util.promisify(fs.readFile);

readFilePromise("index.html").then(data => {
	console.log(data);
})
.catch(err => console.error("Error Reading File!", err))
```

A little verbose, but it does the job. We can further improve this code by using the `async`/`await` feature, which aimed to make asynchronous JavaScript code read like synchronous code. Here's the above example rewritten to use `async/await`.

```javascript
const util = require("util");
const fs = require("fs");

const readFilePromise = util.promisify(fs.readFile);
async function readFile() {
	try {
		const data = await readFilePromise("index.html");
	} catch (err) {
		console.error("Error reading file!", err);
	}
}

readFile();
```

Promises were actually added to Node in June 2009, but removed again in February 2010 to keep things simple in the NodeJS codebase. This was a regret of Ryans, as the unified usage of promises would have sped up the delivery and standardisation or `async/await`. 

Deno supports promises out of the box and each of it's asynchronous APIs were written with Promises in mind. Not having to mix and match promises and callbacks provides a simpler, more consistent asynchronous programming experience. Let's see how we perform the same task as above using the deno standard library.

```javascript
const file = await Deno.open("index.html");
  
try { 	
  await Deno.readFile(file, Deno.stdout);
} catch (err) {
	console.error("Error reading file!", err);
}
  
file.close();
```

No callbacks. All the deno async APIs will return a promise to us.

### Regret: Security

Node has access to several things by default that could be considered sensitive. This includes the file system, network and system calls. Dahl wishes that he had put more thought and effort into security in NodeJS. The example he gave was running a linter. Linters like ESLint probably should not have full access to your computer and network. Unfortunately, they do.

Deno, however, is secure by default. The runtime will not allow you to access the file system, or perform network calls unless you explicitly give it permissions. We'll get into some examples of this later on.

### Regret: The Build System (GYP)

According to Ryan Dahl, the NodeJS build system is his biggest regret.

As much as we'd all like to, we cannot write **everything** in JavaScript. Sometimes we need much better performance than JavaScript can provide. This is where we need to compile native modules written in lower level languages like C++. 

If you are writing a NodeJS module that interfaces to a C++ libary, your code will make use of [GYP (Generate Your Projects)](https://gyp.gsrc.io). GYP and more specifically [node-gyp](https://github.com/nodejs/node-gyp) is a tool that is used to compile native nodeJS modules written in C or C++ so they can be accessed with `require`, like any other JS module. 

Node originally used GYP because Google Chrome used to use GYP heavily. The chrome team later switched over to [GN](https://chromium.googlesource.com/chromium/src/tools/gn/+/48062805e19b4697c5fbd926dc649c78b6aaa138/README.md). GN aims to be a more readable and maintainable file format than GYP. It also offers significantly better performance.

This left Node in limbo as the sole user of GYP. Deno seeks to solve this by using a mixture of GN and [cargo](https://doc.rust-lang.org/book/ch01-03-hello-cargo.html), the build system for Rust. This provides much better build performance and more friendly APIs for compiling native modules.

### Regret: Package.json

`package.json` is the cornerstone of modern JavaScript. It includes the dependencies for your project and tells Node what to install when you run an `npm install`. [NPM](https://en.wikipedia.org/wiki/Npm_(software)) is a centralised repository of JavaScript packages and it is where you will download a vast majority of JS packages from. As JavaScript developers, we generally do not go for long without having to interact with NPM in one way or another.

Dahl regrets `package.json` for several reasons. He does not like the fact that you have to define your dependencies in 2 places - `package.json` and also inside your code when you `require` the module. He also regrets the fact that this is not an abstraction that exists on the web, since you can just add a `<script />` tag with a URL to your HTML.

`package.json` also contains a bunch of other information, such as the name, description and repository of your project. Dahl calls this "noise" and believes that it is unneccessary.

Deno looks to solve this by removing the concept of `package.json` **altogether**. Deno allows you to require modules directly from URLs, drastically simplifying the whole concept of requiring external modules in your scripts and applications.

### Regret: node_modules

![img](https://slack-imgs.com/?c=1&o1=ro&url=https%3A%2F%2Fpbs.twimg.com%2Fmedia%2FEXeWPHeWAAAWR09.png)

So `package.json` says which modules you need, but the modules themselves are installed in your `node_modules` directory inside your project. Every project has it's own `node_modules` folder, requiring you to duplicate dependencies across projects. This concept deviates a lot from the way dependencies work in the browser.

Deno takes a fresh approach to this. `node_modules` does not exist in Deno. When you require an external module in Deno, it is downloaded and your dependencies are stored in a specific directory defined by you. Your program will not fetch the same modules again however, as deno caches dependencies for you.

### Regret: require("module") without extension (".js", ".ts")

In Node, you can require files without the `.js` extension. This creates the need to query the file system and check what you *actually* meant when the node module loader is resolving your modules. Ryan argues that this is less explicit and deviates from the way it's done in the browser, which looks something like the following:

```
<script src="myscript.js" type="text/javascript"></script>
```

Deno enforces the `.js` or `.ts` extension on imports to alleviate this.

### Regret: index.js

In Node, if you `require` a module,   `index.js` is generally used as an "entry" file to a module or application. This is similar to how `index.html` is resolved by default in a web server. This means you can require a folder like this.

```javascript
const mymodule = require("./my-cool-module")
```

Node will look for a `index.js` file inside the `my-cool-module` directory by default to resolve the module. The same is true for modules imported from `node_modules`. Dahl regrets this because it is not explicit, but more practically, it complicated the node module loading system by introducing another implicit step to check for the existence of `index.js`.

## Installing Deno

It's pretty simple to get up and running with deno. On OSX, simply run:

```
brew install deno
```

This command should install the single deno executable on your machine. You can check that worked by running:

```
deno --version
```

If that didn't work, or you are on another OS, check out the [deno installation guide](https://deno.land/manual/getting_started/installation).

## Features

Now that we have deno up and running, let's dive into some of the shiny new features.

### TypeScript Support

TypeScript is a first class citizen of Deno. This means that we can write our code in TypeScript without having to wory about adding any tooling for our compilation step. When we run TypeScript code with deno, it will type check and compile it for us and run it as if it were normal JavaScript. This completely removes the overhead of setting up the TypeScript compiler in your project.

### No NPM - Deno Packaging

Deno takes a fresh and different approach to importing external code. It has no package manager. You can run some external code by simply passing a URL to the `deno run` command.

```bash
deno run https://deno.land/std/examples/welcome.ts
```

This command will pull down the module from the internet, compile it, cache it indefinitely and run it, all without NPM. If you want to `import` a module and use it in your code, you don't have to install a module from NPM and `require` it. Let's say we create the following file, `test.ts`.

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

assertEquals(2, 2);
```

We can then run this with:

```
deno run test.ts
```

Our module will resolve modules from the web and run the code. The next time you do this, deno doesn't have to fetch the module again as it is cached. We can run code from **any** publicly accessible URL. For example, if you want to run some code directly from github, you could use the following command:

```
deno run https://github.com/shogunpurple/deno-test/blob/master/src/hello.ts
```

### Secure Runtime Permissions

![collin-armstrong-8FxJi5wuwKc-unsplash](https://martinmck.com/images/collin-armstrong-8fxji5wuwkc-unsplash.jpg)

Deno is a secure runtime. This means that it operates on the principle of only giving permissions that you supply explicitly. This differs to NodeJS in that node automatically gives permissions to things like your file system and network. Some things that deno will not let you do without explicitly enabling permissions are:

* Read/Write from the File System
* Make HTTP requests and access the network
* Run subprocesses

To demonstrate this, let's work through an example. We are going to run a script that reads a file on the file system. 

```
$ deno run https://deno.land/std/examples/cat.ts /etc/passwd
```

We get the following output:

```
$ deno run https://deno.land/std/examples/cat.ts /etc/passwd  
error: Uncaught PermissionDenied: read access to "/etc/passwd", run again with the --allow-read flag
    at unwrapResponse ($deno$/ops/dispatch_json.ts:43:11)
    at Object.sendAsync ($deno$/ops/dispatch_json.ts:98:10)
    at async Object.open ($deno$/files.ts:37:15)
    at async https://deno.land/std/examples/cat.ts:4:16
```

Whoops. This happens because deno doesn't allow us to read from the file system unless we specify file reading permissions in our `deno run` command.

```
$ deno run --allow-read=/etc https://deno.land/std/examples/cat.ts /etc/passwd
```

This works! You will now see the contents of the `/etc/passwd` file in your terminal output. This provides a powerful layer of security that allows us to configure only the exact permissions we want our program to have when it's run. Some other examples of things you may want to give your program permissions for when running are:

* `--allow-net` - allow HTTP requests from your program
* `--allow-env` - allow access to environment variables.
* `--allow-run` - allow your program to run sub-processes.

Most of these flags take arguments, allowing you to restrict access to particular resources. For example, if you only want to allow your program to send HTTP requests to https://myapi.com:

```
deno run --allow-net=https://myapi.com my-program.ts
```

### Standard Library

The limited standard libary in Node has always been a bit of a sticking point for developers. There is heavy reliance on external modules to perform tasks in Node that are included in the standard library of many other programming languages. UUID generation is an example of this, where the [uuid](https://www.npmjs.com/package/uuid) library is the de facto solution for Node developers, however it is not part of the node standard library. Deno provides an extensive standard library based on that of the [go programming language](https://golang.org/) that includes some nice functionality such as:

* date/time functions
* http servers
* logging
* permissions
* testing
* uuid generation
* websockets
* file system utilities (fs)
* hashing and cryptography
* parsing command line flags

Being able to do this stuff without external modules gives deno the ability to build a myriad of different applications with just the standard library.

### Browser Compatible APIs

Also included in the deno standard library are an impressive set of APIs that adhere to web standards and therefore can be run in the browser, allowing code written and compiled with deno to be run on the client as well as the server! 

One of the most important ones that deno includes is an implemention of `fetch`, a browser API used for making HTTP requests. In node, you need to import an external module for this like `node-fetch`, or use the native `http` module in node, which is a little clunky and verbose. If we wanted to use `fetch` to make a call to google.com with deno, we could use the following code:

```javascript
const response = await fetch("http://www.google.com");

console.log(response);
```

Also notice how we are able to use a top level `await` here - another feature deno supports out of the box. Let's run our code above with:

```
deno run --allow-net fetch_google.ts
```

The result of our HTTP call to google will be shown in the console. Notice how we specified the `allow-net` permissions to allow our code to make an HTTP request. Here are some other common browser APIs that deno supports.

* `addEventListener`
* `removeEventListener`
* `setInterval`
* `clearInterval`
* `dispatchEvent`

You can see the full list of web compliant APIs in deno [here](https://doc.deno.land/https/raw.githubusercontent.com/denoland/deno/master/cli/js/lib.deno.shared_globals.d.ts). 

Complying to web standards will make the deno API much more future proof and provides utility for front-end developers.

## Deno Tools

As well as the actual language features above, deno gives us additional tooling that performs tasks currently carried out by the likes of webpack, rollup, and prettier. The difference is that deno includes these tools out of the box.

### Bundling

![isaac-caffeina-WlTDg-afhaQ-unsplash](https://martinmck.com//images/isaac-caffeina-wltdg-afhaq-unsplash.jpg)

Bundling is the process of taking your application and dependencies, and outputting it into a single JavaScript file that can be executed. This job is generally performed by module bundlers such as rollup, webpack and parcel. Deno gives us a simple approach for bundling code with the `deno bundle` command. If we want to bundle some code, we can do the following with deno.

```
$ deno bundle https://deno.land/std/examples/echo_server.ts server.bundle.js
Bundling https://deno.land/std/examples/echo_server.ts
Download https://deno.land/std/examples/echo_server.ts
Warning Implicitly using master branch https://deno.land/std/examples/echo_server.ts
Emitting bundle to "server.bundle.js"
2661 bytes emmited.
```

We can now run our bundle like any other normal script.

```
$ deno run --allow-net server.bundle.js
Listening on 0.0.0.0:8080
```

### Built in Testing

Deno has a built in test runner that allows us to test our JavaScript and TypeScript code. If you are familiar with JavaScript testing libraries such as Jest or Jasmine, this syntax will look familiar.

```javascript
Deno.test("deno test", () => {
	 const name = "John";
	 const surname = "Wick";
	 const fullname = `${name} ${surname}`;
   assertEquals(fullname, "John Wick");
});
```

We use the `test` functionality on the `Deno` namespace to create a test. We can then run our tests with the `deno test` command:

```
$ deno test test.ts
Compile file:///Users/martinmckeaveney/Development/deno-test/.deno.test.ts
running 1 tests
test deno test ... ok (4ms)

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (5ms)
```

This is a powerful feature, as you could just run a full test suite by pointing your `deno test` command to a test suite hosted somewhere online, without having to pull down and run the test suite yourself.

### Script Installation

Packaging your scripts up into a single executable is a very useful thing when you want someone to be able to run your script on their machine without installing Node. Currently if you want to do this with a node script, this can be done with the fantastic [pkg](https://github.com/zeit/pkg) module by vercel, which requires you to install that external module through NPM.

Deno provides a built in script installer, allowing you to distribute your scripts as a single executable. Let's see how it works. We will install the basic deno "Hello World" script from the deno.land website as an executable script.

```
$ deno install 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
✅ Successfully installed welcome
/Users/martinmckeaveney/.deno/bin/welcome
ℹ️  Add /Users/martin/.deno/bin to PATH
    export PATH="/Users/martin/.deno/bin:$PATH"
```

Scripts are saved by default to the `.deno/bin` folder located in our home directory. We can execute our script directly:

```
$ ./.deno/bin/welcome 
Welcome to Deno 🦕
```

As mentioned in the message above, if we want to execute this script from anywhere on our system, we need to add the `.deno` directory to our `PATH`. The `PATH` variable tells our machine where to look for scripts when we execute them from the terminal. Once we add the `.deno` directory to our path, we can run the script from anywhere!

```
$ welcome
Welcome to Deno 🦕
```

### Formatting

[Prettier](https://prettier.io) is the de facto formatter for JavaScript code. Deno provides a built in formatter (that actually uses prettier under the hood) through the `deno fmt` command.

We can format some ugly looking code in this `test.ts` script by running `deno fmt` on it.

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

Deno.test("deno test", () => { const name = "John"; const surname = "Wick"; 
const fullname = `${ name} ${surname}`;
   assertEquals(fullname, "John Wick"); }
    )
  ;
```

```
$ deno fmt
```

Our result is nicely formatted code.

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

Deno.test("deno test", () => {
  const name = "John";
  const surname = "Wick";
  const fullname = `${name} ${surname}`;
  assertEquals(fullname, "John Wick");
});
```

`deno fmt` provides a nice utility by default that can be a little bit of effort to set up in standard JavaScript projects. Some other commands supports by `deno fmt` include:

* `deno fmt --check` - check if files are already formatted
* `deno fmt file1.ts` - format specific files

## Downsides

A radical simplification and completely different approach to a JavaScript runtime is not without its limitations.

### Backwards Compatibility with Node

Deno is not the "next version" of node. It is a brand new implementation that is **no longer compatible** with NPM packages and existing node modules. The https://deno.land/std/node/ API is being built to allow for this, but at the time of writing this is not complete. There are quite a few ports of existing node modules on the deno website that you can use. Check them out on the [deno.land website](https://deno.land/x). As Deno matures, this list of libraries ported over for deno support will grow exponentially.

### TypeScript Compiler

Deno uses the TypeScript compiler to check types and compile your code into JavaScript. This provides a bottleneck where your code must go through that extra compilation and type checking step before it runs. The TypeScript compiler is written in TypeScript, which means type checking performance in TypeScript will never match the raw performance of native languages like C++ or Rust. There are potential plans to implement the type checking for deno in rust, which is a huge project and won't be ready for quite a long time.

This limitation primarily applies to much larger projects, where the time compiling TypeScript code becomes more of an issue as the codebase grows. For most small to medium projects, this will not be an issue for most developers.

## Conclusion

The internet loves dichotomies. 

A lot of people want to know if they should drop the thing they are learning to move to the hot new one on an almost daily basis, for fear of being left behind. You can see evidence of this with the regular reddit posts and tweets screaming the title "is <technology> dead?!", or the commonly thrown around quip - "You shouldn't use (mature and well established technology), (shiny new thing) is better, because of (feature)". 

Deno is not a replacement for NodeJS. There are too many node programs that currently exist, with many more being written every hour of every single day. Deno is a fresh take and new ethos towards building, packaging and distributing scripts and applications in the JS ecosystem, built on modern technologies with a particular focus on providing a powerful scripting environment with the tooling you need built in. 

Deno still has a long way to go. In order for Deno to become a success, the community must use it and build modules for it. Deno must find it's place in the daily workflow of JavaScript developers. 

One of the problems up to this point with modern JavaScript is the heavy reliance on tooling. Although JS tooling has opened doors that were never thought possible in the past, it can be overwhelming for beginners and even experienced developers who aren't familiar with the JS ecosystem. Deno hides a lot of this away and makes those decisions for you. This is a huge plus for a lot of engineers.

I hope you learned something. Thanks for reading!

Feel free to reach out to me or follow me on [Twitter](https://twitter.com/shogun_purple), where I tweet and blog about JavaScript, Python, AWS, automation and no-code development.