loading...
Cover image for Deno? It's spelled `Dinu` actually...

Deno? It's spelled `Dinu` actually...

hyphaebeast profile image Jonathan Dinu Originally published at hyphaebeast.club on ・14 min read

So I decided to give this new Deno thing a look see, mainly due to its similarity with my last name1, and boy see did I. First a little bit of a lightning tour of where it came from and why it matters.

New…. speaking relatively. Deno is ~2 years old at this point but it recently (five-ish months ago) passed 1.0 and now seems as good a time as any to write this post. (cover image by hashrock from the Deno artwork page)

It’s Node 2.0.

Alright, now that we got that quick tour out of the way. We ca- [screech and rewind sound effect]

A short Node.js diversion

Ryan Dahl created Deno to fix all of the horrible horrible mistakes2 he made when designing Node.js and I’m sure forever regrets. But the secrets to what Node 2.0 is…. are hidden in what Node 1.0 was meant to be. In Ryan Dahl’s own words:

Node stems primarily from trying to solve the upload progress bar problem

But we have perfectly functional upload progress bars?!? Ok ok ok, now we have perfectly functional upload bars possible with web standards and servers themselves. But but but. but. For all the Gen-Z’ers out there… we didn’t always have upload progress bars. In the dark days of the internet (circa 2006) if you wanted to show a user how much of their “very large file” has uploaded successfully you had to jump through:

  • Ajax hoops in client JavaScript to not completely freeze the webpage
  • Server hoops to not complete block the single thread of the Python/Ruby web server

The TLDR; of why a new “language” and why Javascript (on the server):

  1. Interpreter GILs are a feature of programming languages rather than server frameworks
  2. When responding to a web request, a significant amount of server processing time is spent waiting (e.g. for a database query to return)
  3. JavaScript was designed to be asynchronous (since browsers used to be limited to a single thread per page)

A very important aside to understanding Deno is understanding that Node.js wasn’t a new programming language but rather a new runtime for an existing language (i.e. Javascript)

So Node.js really gave us a powerful JavaScript runtime for creating asynchronous web servers. And as its popularity grew folks started using it for non-webserver things that typically were done in other scripting languages (like Perl, Ruby, Python, etc.). But what made Node.js fun and productive for building web servers and microservices, became a liability for userland CLI applications and general purpose scripting. Deno fixes these.

Hello Deno

In a sentence (ripped from the Deno manual):

Deno aims to be a productive and secure scripting environment for the modern programmer.

The key words in that description: productive, secure, and modern. We will go through these one at a time to hopefully understand why Deno was needed, how it accomplishes these things, and when it makes sense to use it.

Most of the content in this post comes from the excellent Deno manual, if you want to learn more definitely check. it. out.

Productive

Rust has been one of the more exciting newish programming languages. And it is exciting for a variety of reasons, but one of the empowering features of it is the ergonomic developer tooling built into the language itself. And Deno feels like if that same ethos was applied to Node and JavaScript3.

To start, Deno made a lot of both design as well as convention decisions early on (I imagine to help prevent a fragmentation of the packages/community). And as a result, the developer (especially one new to the langauge) faces much much less analysis paralysis.

  • Probably the biggest productivity boon is the fact that Deno 👏 is 👏 just 👏 JavaScript (or TypeScript). No new language to learn. Use (most) exisiting JavaScript/TypeScript/Nodejs with little to no change… If you know JavaScript you know Deno
  • Not sure what style to write your code in? Deno’s got your back with deno fmt (rather than a proliferation of competing options) to keep you productive and sane.
  • Not sure which logging library to use? Lucky for you Deno only has one4 (in the standard library) that is guarnteed to work (and work with all the other modules in the standard library)!
  • Want to publish a new package (or even just a single file)? Modules can be referenced and imported directly from URLs (and as such you don’t even need a package manager)
  • Wait wait wait. If there is no package manager, how will I resolve all the dependencies of my “complex code”?!?! Again, Deno’s got you 👌 Just one deno info away
  • And Deno has many more developer tools built into the language…

While each of these points may seem somewhat incidental, together they ease the cognitive burden on the developer resulting in a much more productive and enjoyable experience.

Secure

Probably the most unique/novel aspect of Deno (compared to other scripting languages) is its secure-by-default permissions based security model. Taking inspiriation from (I imagine) browser security standards5, by default the Deno runtime blocks file, network, and environment variable access and it is up to whomever is running the code to specify what is allowed. While this might seem a heavy handed approach, it is actually surprising that other scripting langauges don’t prevent these types of access. Just think of all the times you have blindly used npx/brew/npm/pip/etc to run an unknown script/library from Github that you just, like, kinda trust 🤷6.

Well Deno thankfully says NO MORE! Unless you are ok with it. But then that’s on you 👉 if you leak your AWS_SECRET_ACCESS_KEY to that Node library for coloring your console.log statements that you just haaaaaaaaaadddddd to have 😩

If no flags are specified when a script is deno run and the script tries to make an HTTP request, read/write to the file system, access environment variables, etc. an error is thrown (and if not caught the process exits).

$ deno run https://deno.land/std/examples/curl.ts https://example.com
    error: Uncaught PermissionDenied: network access to "https://example.com/"
    run again with the --allow-net flag
$ 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

For a full list of all the permissions flags, see: https://deno.land/manual/getting_started/permissions

Modern

While JavaScript has evolved since ECMAScript 1, the nature of a standardized language (that all browser vendors need to agree on) is a slow “design by committee” evolution. So while we got a fairly universal runtime—ironically much more portable than its namesake—we so also inherited a somewhat ossified language, hence the proliferation of X-Script transpilers and even a pluggable debuggable JavaScript transcompiler toolchain to “extend” the language without giving up the write-once-run-anywhere-with-a-browser aspect of standards compliant ECMAScript…

Being beholden to no large international industry association, Deno can truly evolve at “internet speed” and incorporate tomorrow’s latest and greatest extentions to the language today. But wait, this free wheeling devil-may-care attitude sounds UNSTABLE AND RISKY?!? What guarantees do I have that the code I write today will run anywhere tomorrow?

To avoid reinventing the curl, Deno mirrors web APIs when possible (so you can just fetch() files on the server or in the browser, for example). And as long as your Deno program is written completely in JavaScript (doesn’t use the global Deno namespace to do non-browser things like reading/writing files), it will run in the browser. So you can write your Deno program in modern JavaScript (with the latest and greatest async/await control flow) or TypeScript and deno bundle it with all its dependencies into a single file self contained ES module that you can import in a <script> tag…

<script type="module">
    import { foo, bar } from "./lib.bundle.js";
</script>

Many of the points made under modern actually make Deno much more productive as well (and vice versa), but rule of three amirite?

Let’s build a CLI!

We can talk for days and days but we all know talk is cheap. So let’s make something! For this demo application, we will be building a terminal application that can fetch the weather forecast for a given location à la https://wttr.in (but much less fancy). This example hopefully is:

  • complex enough to showcase unique aspects of Deno (like using the std library and network permissions).
  • simple enough to be able walk through in a “not super long” blog post.

The full code can be found at here and the complete script can be run simply with:

$ deno run --allow-net=api.weather.gov \
    https://paste.sr.ht/blob/4c0b0fa9919c5e7b76b4893faa5e2eabd26ce7d3 \
    --lat=44.97 --long=-93.26 --duration=24
The forecast for Minneapolis, MN for the next 24 hours 🔮

11 PM Mostly Clear 51 ℉ 5 mph NE 🌬
12 AM Mostly Clear 49 ℉ 0 mph ENE 🌬
1 AM Mostly Clear 48 ℉ 0 mph ENE 🌬
2 AM Mostly Clear 47 ℉ 0 mph ENE 🌬
3 AM Mostly Clear 46 ℉ 0 mph ENE 🌬
4 AM Mostly Clear 45 ℉ 0 mph ENE 🌬
5 AM Mostly Clear 44 ℉ 5 mph ENE 🌬
6 AM Mostly Clear 43 ℉ 0 mph ENE 🌬
7 AM Sunny 42 ℉ 0 mph E 🌬

Installing Deno

To start you are going to need to install Deno, thankfully you can get it with a single command:

Code snippets were run with Deno v1.4.0

deno upgrade --version 1.4.0

curl -fsSL https://deno.land/x/install/install.sh | sh

# test everything installed correctly
deno run https://deno.land/std@0.69.0/examples/welcome.ts

# setup shell autocomplete
deno completions bash > /usr/local/etc/bash_completion.d/deno.bash
source /usr/local/etc/bash_completion.d/deno.bash

The install process is so seamless both because Deno is distributed as a single executable (deno), and also because I imagine the core contributors actually care about a good developer experience. And if you ever need to update Deno (or install a specific version):

# upgrade to latest
deno upgrade

# install specific version
deno upgrade --version 1.0.1

No rvm/rbenv/nvm/pyenv/etc. needed….

Now that you have Deno installed, fire up your favorite text editor to create your first script:

console.log("Hello Deno 🦕")

Running code

Deno has some pretty powerful module/package/dependency conventions and conversely some fun ways to run files. To start we can run a file like in any other programming/scripting langauge. To run the file we just created:

# local file
$ deno run hello_deno.js
Hello Deno 🦕

Nothing too exciting here… Also like most scripting languages we can start up a REPL to run code interactively:

# REPL
$ deno
Deno 1.4.0
exit using ctrl+d or close()
> console.log("Hello Deno 🦕")
Hello Deno 🦕
undefined
>

Again, pretty standard. The real fun comes when we run remote files…. directly from a URL 🤯

deno run https://deno.land/std@0.70.0/examples/welcome.ts

HTTP Requests

Now that we know how to run code… we can start writing code 🙌 For our little weather CLI we first need a place to actually get the weather. Fortunately (for those in the US), the NOAA provides a surprisingly funtional National Weather Service API.

As alluded to before, Deno tries to use Web APIs whenever possible so the Javascript you write is as close to code that could run in a browser. Can you make a HTTP request in the browser? Ab.so.lute.ly. How do you do it in Deno? SSDS

# fourth way of running code... STDIN
$ deno run --allow-net - << EOF
  fetch('https://api.weather.gov/gridpoints/MPX/108,71/forecast/hourly')
   .then(res => res.json())
   .then(json => console.log(json));
EOF

I didn’t say it was going to be pretty….

If all went well, you should see a JSON object with the forecast for Minneapolis, MN.

The ability to run code from STDIN is actually very (subtly) powerful as it enables a Deno (and hence JavaScript) script to transparently fit into a UNIX pipeline. It is a shame it is so difficult/verbose in other server side JS environments, especially since JavaScript (and Node) are so well suited to operating on streams.

I don’t know what else to mention about making HTTP requests in Deno since it, like, just uses the fetch() API. Except I guess that is is safer so you have to pass the --allow-net flag…. See, I told you you already know how to program in Deno!

The std

A big philosophical departure of Deno (as compared to JavaScript & Nodejs) is the fact that it has a standard library (hence the need for libraries like the Closure Library and stdlib for JavaScript). While there is no right answer to the question of “should language X have a standard library?”, here are some pros and cons for JavaScript:

Pro Con
Reliable and audited functionality Deciding what to include with
JS’s design-by-committee standards
Smaller web page downloads Bigger runtime
less analysis paralysis less diverse ecosystem
no left-pad gate no exciting left-pad gate drama

There are likely more/better reasons to include a standard library in a programming language…. but I’m not a PL designer, so this table is what you get.

For me personally, I think it is great that I don’t have to npm i uuid or npm i nanoid or npm i short-uuid or npm i uuid-random or ...

For our weather CLI, it won’t be very useful if the user couldn’t specify options on the command line. I mean, what good is a CLI with no I. Deno’s command line arguments parser from the std to the rescue!

// import command line parsing from the std
import { parse } from "https://deno.land/std/flags/mod.ts";

// parse command line flags to get location and duration
const { lat, long, duration } = parse(Deno.args);

Dependency Management

No npm/pip/gem install. No dependeny hell or package.json with dependencies and devDependencies and peerDependencies and bundledDependencies and… well you get the point. Or semver ranges and tarballs and git URLs with <commit-ish> refs.

Just. a. URL. to a file.

Oh yeah, like a bundled/babeled/compiled file on UNPKG you mean?

Nope. Just a raw source file. JavaScript or TypeScript (Deno don’t care). But it also can bundle all the dependencies into a single self contained ES module in a single JavaScript file if that’s your thing with deno bundle7.

Ok, I got it. It just executes a dependency free JavaScript file?

Well, the dependencies are…… COMING FROM INSIDE THE HOUSE!!

[clears throat] I mean, since any remote file can be executed through standard imports and any dependency can be imported ∴ any dependency (and its dependencies (and its dependencies (and it’s …))) can be resolved and referenced through versioned ES module imports.

Putting it All Together

Besides making HTTP requests with fetch() and the standard library, the rest of the weather CLI is really just standard ES6 Javascript. So I won’t belabor things anymore and just provide you with the completed script 👇

// import command line parsing from the std
import { parse } from "https://deno.land/std/flags/mod.ts";

// parse command line flags to get location and duration
const { lat, long, duration } = parse(Deno.args);

// lookup url for forecast specified at lat, long
let res = await fetch(`https://api.weather.gov/points/${lat},${long}`);
const json = await res.json();
const { city, state } = json.properties.relativeLocation.properties;
const url = json.properties.forecastHourly;

// fetch actual forecast data
res = await fetch(url);
const forecast = await res.json();

// limit console output to specified duration
const periods = forecast.properties.periods.slice(0, duration);

console.log(`The forecast for ${city}, ${state}` +
  `for the next ${periods.length} hours 🔮\n`);

// iterate through each hour's forecast
for (let p of periods) {
  const timestamp = new Date(p.startTime);
  const hours = `${timestamp.getHours() % 12 || 12}`.padStart(2);

  // convert 24h time to 12h
  const cycle = (timestamp.getHours() / 12) < 1 ? 'AM' : 'PM';
  const units = p.temperatureUnit === 'F' ? '' : '';

  // add padding to make thing print nicer
  const weather = p.shortForecast.padStart(12);
  const direction = p.windDirection.padEnd(3);

  console.log(`${hours} ${cycle}\t${weather}\t${p.temperature}` +
    `${units}\t${p.windSpeed} ${direction} 🌬`);
}

Hopefully this post gave you a sense of what Deno is, why it does some things differently than Node, and how you can use it to be more productive with your scripting (and choose it over Python/Ruby/Node/Perl).

Next Steps

RTFM — really. it’s quite good 😉


  1. what’s your reason for learning new programming langauges??? ↩︎

  2. His words not mine 😏 ↩︎

  3. Deno core is actually built in Rust so the developers definitely knew about it…. ↩︎

  4. in a rather large philosophical departure from Node… ↩︎

  5. like CORS, CSP, <iframe> sandbox, etc. ↩︎

  6. But I guess that’s why npm audit exists ↩︎

  7. No Rollup or webpack or browserify or etc. required…. ↩︎


To the extent possible under law, Jonathan Dinu has waived all copyright and related or neighboring rights to Deno? It's spelled 'Dinu' actually....


This work is published from: United States.

CC0

Posted on by:

hyphaebeast profile

Jonathan Dinu

@hyphaebeast

Free agent academic and rapscallion researcher... Data, net art, and future web technologies

Discussion

pic
Editor guide