Getting started with Deno 1.0

May 13, 2020
Written by
Reviewed by

getting started with Deno 1.0

Since 2018, the creator of Node.js has been working on a new JavaScript runtime called Deno that addresses some of the pain points he had identified. Deno’s features include:

  •  An improved security model
  • Decentralized package management
  • A standard library
  • Built in tooling

Check out this blog post for a full overview of Deno’s features and how it compares to Node.js.

Deno 1.0 was just released. What better time to dive in? In this post we’ll get started with Deno by building a small project.

When I’m learning a new framework or language, I try to find a Goldilocks project. That is, not too hard, not too soft, somewhere between “hello world” and production ready code.

Let’s write a Deno script that sends you an SMS affirmation. In order to do that we'll use Deno to:

  • Read a file
  • Read environment variables
  • Run a script
  • Import and use modules
  • Make an API call

To follow along, you’ll need:

We’ll be writing TypeScript since Deno has fantastic TypeScript support out of the box. If you are unfamiliar with JavaScript or haven’t used a typed programming language, you may want to read up on TypeScript basics.

Setting up your Deno dev environment (Denovironment?)


First we need to install Deno. Instructions vary depending on your operating system.

Mac and Linux folks: if you’re a Homebrew user, you can brew install deno. If not, try running curl -fsSL https://deno.land/x/install/install.sh | sh.

Windows users can run iwr https://deno.land/x/install/install.ps1 -useb | iex or choco install deno if you use Chocolatey.

To verify that the installation was successful, try running deno --help from the command line. If you have trouble with installation check out the official Deno docs.

Optional, but highly recommended: if you’re using Visual Studio Code install the Deno extension. Otherwise you’ll get annoying Intellisense errors that don’t apply to Deno.

Hello, Deno: running your first script


As is traditional, we’ll write a Hello World script to ensure we can execute code. Add a new file in the top level directory of your project. Call it “send-affirmation.ts”. Copy the following code into it:

import { bgBlue, red, bold } from "https://deno.land/std/fmt/colors.ts";
console.log(bgBlue(red(bold("Hello world!"))));

Run the file with deno send-affirmation.ts on the command line. It should print the “Hello world!” text with some bold colors.

Reading a file with Deno

I know, I know, it’s a little contrived to put the affirmations in a file just so we can use the file-reading API. Since the goal is learning it’s okay to add a bit of unnecessary complexity.

Add a file  called affirmations.txt at the top level of your project directory. I copied the first ten from affirmations.dev but feel free to create your own.

You got this.
You'll figure it out.
You're a smart cookie.
I believe in you.
Sucking at something is the first step towards being good at something.
Struggling is part of learning.
Everything has cracks - that's how the light gets in.
Mistakes don't make you less capable.
We are all works in progress.

Replace the  code in send-affirmation.ts with the following:

const getAffirmations = async (fileName: string): Promise<Array<string>> => {
 const decoder = new TextDecoder("utf-8");

 const text: string = decoder.decode(await Deno.readFile("affirmations.txt"));
 return text.split("\n");
};

const affirmations: Array<string> = await getAffirmations("affirmations.txt");

const affirmation: string =
 affirmations[Math.floor(Math.random() * affirmations.length)];
console.log(affirmation);

A lot of this isn’t too different from plain ol’ TypeScript. At a high level, we are:

  • Using TextDecoder and Deno’s built-in readFile method to read the file
  • Splitting the file into lines and throwing the individual values into an array
  • Picking a random affirmation with Math.random

If we try to run the script with the previous command, we get an error response of

error: Uncaught PermissionDenied: read access to "/Users/tthurium/github/deno-getting-started/affirmations.txt", run again with the --allow-read flag

Deno’s security model requires you to specify exactly which permissions your module needs. You can run deno run --help to see an explanation of the permissions model and all available flags.

This is a huge improvement over Node.js, which allows reading your hard drive, making requests, and all kinds of other potentially sketchy activities by default. Also, it’s great that Deno’s error message tells you exactly how to fix it.

Execute your script by adding the allow-read flag, like so:

deno run --allow-read send-affirmation.ts

Which should get you the following output:

Compile file:///Users/tthurium/github/deno-getting-started/send-affirmation.ts
Everything has cracks - that's how the light gets in.

Deno compiles your TypeScript files for you, without you having to do anything. Which is why the Compile file… line is only printed after you run a file that has changed.

Deno and third-party libraries

The Deno standard library is slick but standard libraries are never gonna give you everything you need. How do helper libraries work?

Unlike Node.js, there is no centralized Deno package manager. Decentralization is bound to be one of Deno’s most controversial design decisions.

Does decentralization make Deno less secure than Node.js? It depends. If you’re using npm 6.0 or above, you can run npm audit to get a list of known vulnerabilities in your dependencies. Deno has no such functionality. npm audit only helps to a point, because unknown vulnerabilities are unknown. At the end of the day, neither npm or Deno are the Apple Store -- with both you’re running code that is fundamentally untrusted. IMO Deno is more secure, because at least Deno requires explicit permission for potentially sketchy activities like reading your hard drive.

Deno modules can be imported from any URL, and they’re cached on your hard drive on the first run. You can read the list of “official” 3rd-party Deno packages here. Or, you can search on Pika for Deno-compatible Node modules.

If we search Pika for the Twilio Node.js SDK, we get Package found! However, no web-optimized "module" entrypoint was found in its package.json manifest.

Well, fine. The Twilio Node.js SDK does make it easier to make Twilio calls, but we’ll learn more from making raw API calls.

The Twilio API uses basic auth, which requires base 64 encoding. We’ll need to use a third-party Deno module for that. To import the base64 library, add the following code at the top of send-affirmation.ts:

import * as base64 from "https://denopkg.com/chiefbiiko/base64/mod.ts";

Reading environment variables in Deno

To do Twilio things, you need your account SID and auth token which are found on the Twilio console. You don’t want to commit these values in code, because if you push them to a publicly accessible repository an attacker could use these credentials to do bad things with your account. Set these values as environment variables instead.

To double check that your environment variables are stored correctly, add the following code to the bottom of send-affirmation.ts:

const accountSid: string | undefined = Deno.env.get("TWILIO_ACCOUNT_SID");
const authToken: string | undefined = Deno.env.get("TWILIO_AUTH_TOKEN");
console.log(`accountSid: ${accountSid}, authToken: ${authToken}`);

To run this code we'll need to add another flag to enable reading environment variables.  From the command line, run `deno run --allow-read --allow-env --send-affirmation.ts` to validate that the variables you just added have been set correctly. Make sure to delete this `console.log` statement afterwards to not expose your credentials in your logs accidentally. Making a POST request in Deno In addition to the account SID and auth token, the Twilio API requires a phone number to send to and from if you're sending a text message. Copy this code into `send-affirmation.ts` and replace the phone numbers with the ones you want to use.
const accountSid: string | undefined = Deno.env.get("TWILIO_ACCOUNT_SID");
const authToken: string | undefined = Deno.env.get("TWILIO_AUTH_TOKEN");
const fromNumber: string = "Replace with your Twilio number";
const toNumber: string = "Replace with your cell number";

Next we’ll add a new function to make the request to the Twilio API.

Deno uses the same API as the fetch method that’s built in to the browser. Having to rely on third-party packages for basic functionality is one of JavaScript’s biggest frustrations. Thanks Deno developers for having a sensible standard library!

const sendTextMessage = async (
 messageBody: string,
 accountSid: string | undefined,
 authToken: string | undefined,
 fromNumber: string,
 toNumber: string,
): Promise<any> => {
Yeah I know, this is a lot of params and some of these could be defined inside the function. At the same time, I prefer to pass in args because it makes it easier to write unit tests. Not that I'll be writing unit tests for this code, but it's the principle of the thing. 😆 If the account credentials aren't set throw a friendly error message:
 if (!accountSid || !authToken) {
   console.log(
     "Your Twilio account credentials are missing. Please add them.",
   );
   return;
 }

Inside the sendTextMessage function body add the following code to encode Twilio credentials.

 const url: string =
   `https://api.twilio.com/2010-04-01/Accounts/${accountSid}/Messages.json`;

 const encodedCredentials: string = base64.fromUint8Array(
   new TextEncoder().encode(`${accountSid}:${authToken}`),
 );

We’ll finish out our function by actually making the API call.

x-www-form-urlencoded requires URL-encoded params in the request body. To the URLSearchParams mobile, Batman!

 const body: URLSearchParams = new URLSearchParams({
   To: toNumber,
   From: fromNumber,
   Body: messageBody,
 });

 const response = await fetch(url, {
   method: "POST",
   headers: {
     "Content-Type": "application/x-www-form-urlencoded",
     "Authorization": `Basic ${encodedCredentials}`,
   },
   body,
 });
 return response.json();
};

Of course, we actually need to call the function.

const response = await sendTextMessage(
 affirmation,
 accountSid,
 authToken,
 fromNumber,
 toNumber,
);

console.log(response);

You can see the entire script on GitHub.

To run this script, we need one more command line argument to allow the script to make network requests.

From the command line, run:

deno run --allow-read --allow-env --allow-net send-affirmation.ts

You should receive a text message containing one of the affirmations. Well done! 🦕🎉

Conclusion: building your first Deno app

Today you have learned a few things about Deno, like how to:

  • Make POST requests
  • Read from a file
  • Deal with environment variables
  • Understand Deno’s permissions model
  • Import modules in Deno
  • Use some of Deno’s standard library functionality

Are you building something cool with Deno? Tell me about it! Hit me up on Twitter or over email (tthurium {at} twilio {dot} com).