In this article, we’ll explore Deno, a relatively new tool built as a competitor/replacement for Node.js that offers a more secure environment and comes with TypeScript support out the box.
We’ll use Deno to build a command-line tool to make requests to a third-party API — the Star Wars API — and see what features Deno provides, how it differs from Node, and what it’s like to work with.
Deno is a more opinionated runtime that’s written in TypeScript, includes its own code formatter (deno fmt
), and uses ES Modules — with no CommonJS require
statements in sight. It’s also extremely secure by default: you have to explicitly give your code permission to make network requests, or read files from disks, which is something Node allows programs to do by default. In this article, we’ll cover installing Deno, setting up our environment, and building a simple command-line application to make API requests.
As ever, you can find the code to accompany this article on GitHub.
Installing Deno
Table of Contents
You can check the Deno website for the full instructions. If you’re on macOS or Linux, you can copy this command into your terminal:
curl -fsSL https://deno.land/x/install/install.sh | sh
You’ll also need to add the install directory to your $PATH
.
Don’t worry if you’re on Windows, as you can install Deno via package managers such as Chocolatey:
choco install deno
If Chocolately isn’t for you, deno_install lists a variety of installation methods, so pick the one that suits you best.
You can check Deno is installed by running the following command:
deno -V
This should output the Deno version. At the time of writing, the latest version is 1.7.5, which is what I’m using.
If you’re using VS Code, I highly recommend installing the Deno VS Code plugin. If you use another editor, check the Deno documentation to find the right plugin.
Note that, if you’re using VS Code, by default the Deno plugin isn’t enabled when you load up a project. You should create a .vscode/settings.json
file in your repository and add the following to enable the plugin:
{ "deno.enable": true }
Again, if you’re not a VS Code user, check the manual above to find the right setup for your editor of choice.
Writing Our First Script
Let’s make sure we have Deno up and running. Create index.ts
and put the following inside:
console.log("hello world!");
We can run this with deno run index.ts
:
$ deno run index.ts Check file:///home/jack/git/deno-star-wars-api/index.ts hello world
Note that we might see a TypeScript error in our editor:
'index.ts' cannot be compiled under '--isolatedModules' because it is considered a global script file. Add an import, export, or an empty 'export {}' statement to make it a module.ts(1208)
This error happens because TypeScript doesn’t know that this file is going to use ES Module imports. It will soon, because we’re going to add imports, but in the mean time if we want to remove the error, we can add an empty export
statement to the bottom of the script:
export {}
This will convince the TypeScript compiler that we’re using ES Modules and get rid of the error. I won’t include this in any code samples in the blog post, but it won’t change anything if we add it other than to remove the TypeScript noise.
Fetching in Deno
Deno implements support for the same Fetch API that we’re used to using in the browser. It comes built into Deno — which means there’s no package to install or configure. Let’s see how it works by making our first request to the API we’re going to use here, the Star Wars API (or SWAPI).
Making a request to https://swapi.dev/api/people/1/
will give us back all the data we need for Luke Skywalker. Let’s update our index.ts
file to make that request. Update index.ts
to look like so:
const json = fetch("https://swapi.dev/api/people/1"); json.then((response) => { return response.json(); }).then((data) => { console.log(data); });
Try and run this in your terminal with deno run
:
$ deno run index.ts Check file:///home/jack/git/deno-star-wars-api/index.ts error: Uncaught (in promise) PermissionDenied: network access to "swapi.dev", run again with the --allow-net flag throw new ErrorClass(res.err.message);
Deno is secure by default, which means scripts need permission to do anything that could be considered dangerous — such as reading/writing to the filesystem and making network requests. We have to give Deno scripts permissions when they run to allow them to perform such actions. We can enable ours with the --allow-net
flag:
$ deno run --allow-net index.ts Check file:///home/jack/git/deno-star-wars-api/index.ts { name: "Luke Skywalker", ...(data snipped to save space)... }
But this flag has given the script permission to access any URL. We can be a bit more explicit and allow our script only to access URLs that we add to an allowlist:
$ deno run --allow-net=swapi.dev index.ts
If we’re running scripts that we’re authoring ourselves, we can trust that they won’t do anything they shouldn’t. But it’s good to know that, by default, any Deno script we execute can’t do anything too damaging without us first allowing it permission. From now on, whenever I talk about running our script in this article, this is the command I’m running:
$ deno run --allow-net=swapi.dev index.ts
We can also write this script slightly differently using top level await, which lets us use the await
keyword rather than deal with promises:
const response = await fetch("https://swapi.dev/api/people/1/"); const data = await response.json(); console.log(data);
This is the style I prefer and will use for this article, but if you’d rather stick to promises, feel free.
Installing Third-party Dependencies
Now that we can make requests to the Star Wars API, let’s start thinking about how we want to allow our users to use this API. We’ll provide command-line flags to let them specify what resource to query (such as people, films, or planets) and a query to filter them by. So a call to our command-line tool might look like so:
$ deno run --allow-net=swapi.dev index.ts --resource=people --query=luke
We could parse those extra command-line arguments manually, or we could use a third-party library. In Node.js, the best solution for this is Yargs, and Yargs also supports Deno, so we can use Yargs to parse and deal with the command-line flags we want to support.
However, there’s no package manager for Deno. We don’t create a package.json
and install a dependency. Instead, we import from URLs. The best source of Deno packages is the Deno package repository, where you can search for a package you’re after. Most popular npm packages now also support Deno, so there’s usually a good amount of choice on there and a high likelihood that you’ll find what you’re after.
At the time of writing, searching for yargs
on the Deno repository gives me yargs 16.2.0. To use it locally, we have to import it from its URL:
import yargs from "https://deno.land/x/yargs/deno.ts";
When we now run our script, we’ll first see a lot of output:
$ deno run --allow-net=swapi.dev index.ts Download https://deno.land/x/yargs/deno.ts Warning Implicitly using latest version (v16.2.0-deno) for https://deno.land/x/yargs/deno.ts Download https://deno.land/x/yargs@v16.2.0-deno/deno.ts Download https://deno.land/x/yargs@v16.2.0-deno/build/lib/yargs-factory.js Download https://deno.land/x/yargs@v16.2.0-deno/lib/platform-shims/deno.ts Download https://deno.land/std/path/mod.ts Download https://deno.land/x/yargs_parser@v20.2.4-deno/deno.ts ...(more output removed to save space)
The first time Deno sees that we’re using a new module, it will download and cache it locally so that we don’t have to download it every time we use that module and run our script.
Notice this line from the above output:
Warning Implicitly using latest version (v16.2.0-deno) for https://deno.land/x/yargs/deno.ts
This is Deno telling us that we didn’t specify a particular version when we imported Yargs, so it just downloaded the latest one. That’s probably fine for quick side projects, but generally it’s good practice to pin our import to the version we’d like to use. We can do this by updating the URL:
import yargs from "https://deno.land/x/yargs@v16.2.0-deno/deno.ts";
It took me a moment to figure out that URL. I found it by recognizing that the URL I’m taken to when I search for “yargs” on the Deno repository is https://deno.land/x/yargs@v16.2.0-deno
. I then looked back at the console output and realized that Deno had actually given me the exact path:
Warning Implicitly using latest version (v16.2.0-deno) for https://deno.land/x/yargs/deno.ts Download https://deno.land/x/yargs@v16.2.0-deno/deno.ts
I highly recommend pinning your version numbers like this. It will avoid one day a surprising issue because you happen to run after a new release of a dependency.
deno fmt
A quick aside before we continue building our command-line tool. Deno comes with a built in formatter, deno fmt, which automatically formats code to a consistent style. Think of it like Prettier, but specifically for Deno, and built in. This is another reason I’m drawn to Deno; I love tools that provide all this out of the box for you without needing to configure anything.
We can run the formatter locally with this:
$ deno fmt
This will format all JS and TS files in the current directory, or we can give it a filename to format:
$ deno fmt index.ts
Or, if we’ve got the VS Code extension, we can instead go into .vscode/settings.json
, where we enabled the Deno plugin earlier, and add these two lines:
{ "deno.enable": true, "editor.formatOnSave": true, "editor.defaultFormatter": "denoland.vscode-deno" }
This configures VS Code to run deno fmt
automatically when we save a file. Perfect!
Continue reading How to Fetch Data from a Third-party API with Deno on SitePoint.