A Simple Web Server with Deno and Deno Deploy

Deno makes the task of writing your own web server fun again

in 6 min read

Introduction #

Deno is a relatively new JavaScript runtime that includes first-class TypeScript support (TypeScript code can be directly executed in Deno without the need to transpile it to JavaScript first) and aims to be a faster and more secure alternative to NodeJS.

It's built on the venerable Chrome V8 engine and Rust. It supports familiar web platform standards like ES modules, web workers, and fetch. It means that in theory, you could use the same JavaScript code from your server and run it unmodified in the browser. Why you'd want to do that I'm not sure.

Moving on..

In Deno's own words #

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

Sounds good? Let's build a web server with some basic route support to get a feel for this Deno thing.

Installing Deno #

Let's install the Deno binary locally.

Shell command (Linux, MacOS)

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

Or install using Homebrew (MacOS)

brew install deno

You can also install using PowerShell, Scoop, Nix, Cargo etc. The latest binaries can be found here: github.com/denoland/deno/releases

Let's now test that Deno is installed correctly:

deno --version

This should return something like:

deno 1.28.3 (release, aarch64-apple-darwin)
v8 10.9.194.5
typescript 4.8.3

If so, we're good to go.

Optionally set up your dev environment #

Once installed, the Deno CLI contains a lot of the tools that are commonly needed for developing applications, including a full language server to help power your IDE of choice. Most editors integrate directly into Deno using the Language Server Protocol and the language server that is integrated into the Deno CLI.

There is an official extension for VS Code called vscode_deno. When installed, it will connect to the language server built into the Deno CLI.

Plugins are also available for JetBrains IDEs and Vim etc. You can learn more about setting up your environment here: https://deno.land/manual@v1.29.2/getting_started/setup_your_environment

Test with a simple Hello World example #

Browser compatibility means a Hello World program in Deno is the same as the one you can run in the browser.

Create a file called first_steps.ts and copy and paste the code below:

console.log("Welcome to Deno!");

Now cd to your project folder and run the program from the terminal:

deno run first_steps.ts

This should output Welcome to Deno in the terminal.

Let's continue to build our web server.

Building our simple web server #

For this example, we're going to use Oak for our router middleware. Oak is very similar to the Koa router for NodeJS and uses familiar Express-style syntax.

Create a new file server.ts

All of our code for this example will live inside this file.

We first need to import any dependencies to make them available to our program. One of the quirks of Deno is that there is no concept of a package manager; external modules are imported directly into local modules as URLs. There is no npm install or similar step in Deno; the import will download and cache the packages ready for use in your app.

At the top of our file, let's import the Oak middleware framework from Deno's official script-hosting service:

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

Tip: With Deno you can import remote modules from any web location you like e.g. GitHub or an S3 bucket.

Next, let's write the rest of our web server.

For this example, our server will listen on port 8080 to the /say/:greeting route and will parse the entered greeting and output it in the response body. Any non-matching routes will instead output a help message.

Here's how our server.ts code looks now:

// Import the required modules
import { Application, Router } from "https://deno.land/x/oak/mod.ts";

// Create a new instance of the Oak router
const router = new Router();

// Define our greeting route e.g. /say/{greeting}
// It's called for all HTTP verbs/requests matching the path: /say/*
router.all("/say/:greeting", (ctx, next) => {
  // Return the value of `:greeting` from the parsed URL
  ctx.response.body = `Deno says: ${ctx.params.greeting}`;
});

// Define a fallback route for any non-matching paths
router.get("/(.*)", (ctx, next) => {      
  ctx.response.body = "Enter a greeting in the URL e.g. /say/hello";
});

// Create a new instance of the Oak application
const app = new Application();

// Initalize the router middleware
app.use(router.routes());
app.use(router.allowedMethods());

// Listen to requests on the sepecified port
app.listen({ port: 8080 });

You can also view the finished code in this GitHub Gist.

Running the code #

If we try to run our server program using the same deno run server.ts command that we used earlier, it will fail and return an error regarding network access. So what did we do wrong?. You might remember from the introduction that Deno is a runtime which aims to be secure by default. This means you need to explicitly give programs the permission to do certain 'privileged' actions, such as access the network.

Let's try running the command again with the correct --allow-net permission flag:

deno run --allow-net server.ts

Deno will first download and cache any dependencies (in this case the Oak framework) and will start the server.

In a browser, go to: http://localhost:8080/say/hello

The server should respond with the message: "Deno says: hello"

Try entering a path other than /say/{greeting}. It should return a message prompting you to enter a greeting.

That's it! We now have a working web server with some basic routing in just a few lines of code.

Next, let's take a look at getting our example server deployed.

Deployment #

Deno Deploy #

Deno Deploy is a distributed platform from the creators of Deno that allows you to deploy Deno code to its network of V8 isolates spread out over 35 worldwide edge locations. Deno is suited to use at the edge due to its exceptionally short cold start times (traditionally a problem for all serverless functions).

Deno Deploy integrates directly with GitHub so you can push to a branch, review a deployed preview, and merge to release to production.

The easiest way to use Deno Deploy is to sign up for a free account, connect it to a GitHub repository, and deploy your app via the Deploy projects dashboard. There is also a playground where you can experiment with and deploy simple Deno projects.

Alternatively you can install Deno's deployctl command line tool:

deno install --allow-read --allow-write --allow-env --allow-net --allow-run --no-check -r -f https://deno.land/x/deploy/deployctl.ts

Before being able to deploy, you need to get a personal access token from the Deno Deploy access token page. Store this token in a DENO_DEPLOY_TOKEN environment variable, or pass it to deployctl with the --token flag.

Deploying our server.ts project using the CLI would then look like this:

deployctl deploy --project=greeting-server server.ts

To view the CLI help:

deployctl -h

Learn more about using the deployctl command line tool.

DigitalOcean, AWS, Cloudflare and other providers #

Though the easiest and fastest way to deploy Deno is with Deno Deploy, you can deploy Deno projects to any virtual private server. Learn more about Deploying Deno apps to DigitalOcean, AWS, Cloudflare and other providers.

Wrapping up #

In this post I've talked a bit about Deno and shown you how to make a simple web server/router using Oak. Although I haven't gone into too much depth, it hopefully serves as a useful introduction for those of you who haven't explored Deno until now.

If you liked this post or if there is anything you think I could do to improve it (there's probably lots!), please reach out and send me an email.

Further reading #