Understanding Deno: Simplifying JavaScript Runtime and Dependency Management

Written by

Deno, a secure TypeScript runtime, was primarily written by Ryan Dahl (creator of NodeJS). He introduced Deno at his  "10 Things I Regret About Node.js" talk in 2018.

He talked about the importance of allowing runtime flags to control access to the network or file system (--allow-net --allow-write). By default Deno doesn’t allow access for writing or network. This is very useful if you want to allow a list of domains for network access.

Also, when starting a Node project from scratch, the design has two problems: the node_modules folder has a lot of stored dependencies and a centralized package.json file is responsible for the register. Node is notorious for having tons of files in the node_modules folder. See this meme to get a sense of how people view dependency management with node Heaviest Objects in the Universe.

NPM overall is a solid dependency management tool, and when it works it’s great, but when you have a problem the typical solution is something like npm cache clean or delete node_modules and install again.

Deno is looking to simplify the package manager taking advantage of ES modules. The approach for Deno is to import modules from a URL. This is inspired by GoLang. That means no more node_modules or package.json.

If you have been following along with JS development over the last 5 or so years, you will know how big of a deal TypeScript has become a big deal. Deno is TypeScript supported out of the box so we don't need to compile the .ts files. But that doesn’t mean you can’t have javascript files in your project.

Dahl mentioned his need to have “a single executable with minimal linkage.” That makes sense when you look for some tools, like the bundle, for example. According to Deno Docs, bundling is the way that you get a single Javascript file which includes all the dependencies.

Miscellaneous commands at the top level were also mentioned as a Deno goal; the fact we can use the reserved word  await at the top-level is awesome. In this way, you don’t need to wrap it into an async task.  Additionally, most of the browser functionalities are present, for example, you can use fetch, clearInterval or setTimeout from scratch,  you can find the complete list here

According to the Deno tweet on May 12th, this release is ready to go so it's time to take a look at how it works and what Deno offers.

Installation

To install Deno we need to follow the instructions on the Deno homepage; an example in bash installation could be:

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

Next, we simply need to configure our .bashrc to include Deno on the path. 

Getting started

Let's try a simple script creating a main.ts file writing a hello world, and then let’s see how to run it.

/* main.ts */
console.log('Hello world)
/* terminal */
deno run main.ts

Notice we don't need to configure the .ts compilation; Deno does this by default.

What about an HTTP server? First, we need to import the package:

/*  main.ts */
import { serve } from "https://deno.land/std@0.50.0/http/server.ts";
const s = serve({ port: 8000 });
console.log("http://localhost:8000/");

for await (const req of s) {  
	req.respond({ body: "Hello World\n" });
}

Let's run it:

/* terminal */
deno run main.ts
/* terminal output */
error: Uncaught PermissionDenied: network access to "127.0.0.1:8000", run again with the --allow-net flag

Deno is secure by default, so you will need to add some flags to allow network access or file reading depending on what you need. For this example, we need the --allow-net flag.

/* terminal */
deno run --allow-net main.ts

But what about package manager, if we need GraphQL for example?

Importing 3rd Party modules with Pika

Now it’s time to introduce a popular webpage to allow us to find ES package modules called Pika.

Pika is a CDN that provides us a simple way to search packages and take the URL to import into our project.

Let's see how to import GraphQL.

import { 
	graphql, 
    GraphQLSchema, 
    GraphQLObjectType, 
    GraphQLString,
} from "https://cdn.pika.dev/graphql@^14.6.0";

So simple, and we know exactly where this package comes from.

Async Everywhere?

You don't need to wrap await on an async function; just using await reserved word is enough for Deno to handle async/await tasks.

Code Example Async

We can use fetch with await, so doing a request is easier in Deno. Let's see an example:

const res = await fetch('https://jsonplaceholder.typicode.com/todos/1')
const json = await res.json();

Pros and Cons

Can you use NPM modules?

Yes, but they must be an ES module. There are tons of modules on NPM that are not compatible with Deno, so that’s a little awkward; however, as a programmer, you should not need to worry. We expect in the future Deno will find a way to handle this.

Moving from Nodejs

Moving from NodeJS to Deno could be a problem; maybe you need to ask yourself about all the dependencies you’ll need, but if Deno doesn’t support it, your answer could keep to using NodeJS.

Modules cached

Once you have your modules downloaded, Deno doesn’t do it again, but if you want you can force it in the terminal.

deno fetch --reload main.ts

Deno + Postgres

Deno was released on May 13th and we can’t wait to know how it tastes, so let’s do an example of a REST app with basic crud using Postgres as a database driver.

In the beginning, you may feel frustrated because you don’t have npm; you may start to ask, “where can I find this or that package?” And, as you know Deno is new, so there’s a lot of modules that are not supported. 

Maybe Pika or dev.land third-party modules could help you find what you are looking for. But remember, Deno is just a baby.

Recipe

We are going to use the following packages: 

  • Oak for the webserver
  • dotenv to manage environment variables 
  • Postgres as a driver to communicate with the database,

All of them come from deno.land.

Web server listening

Let’s start creating a main.ts file for the webserver.

import { Application, Status } from "https://deno.land/x/oak/mod.ts";
import router from "./routes.ts";

const app = new Application();
app.use(async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    console.log(err);
  }
});
app.use(router.routes());
app.use(router.allowedMethods());

console.log("Listening on 8000");

await app.listen({ port: 8000 });

Router

Now let’s configure the routes; we are going to use get to retrieve the users, post to create a user, put undefined for an update, and delete to remove one of them.

import { Router } from "https://deno.land/x/oak/mod.ts";
import { findOne, findAll, insert, update, remove } from "./users.ts";
const router = new Router();
router
 .get("/users", async (context: any) => {
   context.response.body = await findAll();
 })
 .post("/users", async (context: any) => {
   const result = await context.request.body({
     contentTypes: {
       json: ["application/json"],
     },
   });
 
   const { email, name } = result.value;
 
   try {
     if (await findOne({ email })) {
       context.response.status = 400;
       context.response.body = "user already exists";
       return;
     }
     const res = await insert({ email, name });
     context.response.body = "user inserted";
   } catch (error) {
     context.response.status = 400;
     context.response.body = "something is wrong";
   }
 })
 .put("/users", async (context: any) => {
   const result = await context.request.body({
     contentTypes: {
       json: ["application/json"],
     },
   });
   const { email, name } = result.value;
   await update({ email,  name,  });
   context.response.body = 'user updated';
 })
 .delete("/users", async (context: any) => {
   const result = await context.request.body({
     contentTypes: {
       json: ["application/json"],
     },
   });
   const { email, name } = result.value;
   await remove({ email });
   context.response.body = 'user removed';
 });
export default router;

Postgres client

For database communication, we are going to isolate it in a file called client, export a query function, and execute it.

From Dotenv, we are referring to load.ts; however, when you run the code you might get a warning like:
Warning: In a future version loading from 'https://deno.land/x/dotenv/dotenv.ts' will be deprecated in favour of 'https://deno.land/x/dotenv/mod.ts'.

For this example, we are going to use this module, but I invite you to take a look at a function called config from https://deno.land/x/dotenv/mod.ts that could help with this warning:

import { Client } from "https://deno.land/x/postgres/mod.ts";
import "https://deno.land/x/dotenv/load.ts";

const client = new Client({  
	user: Deno.env.get("PGUSER"),  
    database: Deno.env.get("PGDATABASE"),  
    hostname: Deno.env.get("PGHOST"),  
    password: Deno.env.get("PGPASSWORD"),  
    port: 5432
});

export async function query(queryString:string){  
	await client.connect();   
    const result = await client.query(queryString);  
    await client.end();
    
return result;

If you are using VisualStudio Code I highly recommend you use the REST client extension, which is very helpful when working with HTTP requests.

Running

To start the webserver, we use the deno run command, indicating the flags as optional parameters, and at the end, the name of the file, main.ts.

Why allow-net?

It is mandatory to load the packages from the server and for database communication.

Why allow-env?

We need to read our environment variables, so we need this flag.

Why allow-read?

Dotenv will read the .env file to load the environment variables and load those into deno.env.

HTTP Request

Let’s create a file named users.http and, using the REST client extension, we can test our API.

### Get all users
GET http://localhost:8000/users HTTP/1.1
### Create user
POST http://localhost:8000/users HTTP/1.1
content-type: application/json
{
   "name": "John Doe",
   "email": "jonh@doe.ct"
}
### Update user
PUT http://localhost:8000/users HTTP/1.1
content-type: application/json
{
   "name": "John Doe",
   "email": "jonh@doe.ct"
}
###
DELETE  http://localhost:8000/users HTTP/1.1
content-type: application/json
{
   "email": "jonh@doe.ct"
}

If you want to test it on your own, feel free to refer to this GitHub example.

Release Date

According to @deno_land on May 13th the v-1.0.0 of Deno will be released. As of this post, v1.0.0rc3 has some fixes.

If you already have Deno installed you can use deno upgrade to get the latest version.‍

Links and References

As the title suggests, Dahl discussed the design problems in NodeJS and his regrets about it. For example, security; Node doesn’t require access to write files and that becomes a problem when you are using a lot of plugins that are not yours.

Frequently Asked Questions