A First Look at Oak

ajcwebdev - Nov 2 '21 - - Dev Community

Outline

All of this project's code can be found in the First Look monorepo on my GitHub.

Introduction

Oak is a middleware framework for Deno's native HTTP server and Deno Deploy. It is influenced by Koa (hence the anagram) and includes a middleware router inspired by @koa/router.

Setup

Deno ships as a single executable with no dependencies. However, we will also need to install deployctl for Deno Deploy.

Install Deno Executable

There are various ways of installing Deno depending on your operating system. If you are using Mac or Linux, the official Shell install script is recommend.

curl -fsSL https://deno.land/x/install/install.sh | sh
Enter fullscreen mode Exit fullscreen mode

You can find a list of different installation methods on the official deno.land documentation and the deno_install repo.

Install deployctl

With deployctl you can run your Deno Deploy scripts on your local machine. Your scripts are run in the Deno CLI, with the correct TypeScript types for Deno Deploy. After installing Deno, deployctl can be installed using the following command.

deno install \
  --allow-read \
  --allow-write \
  --allow-env \
  --allow-net \
  --allow-run \
  --no-check \
  --force \
  https://deno.land/x/deploy/deployctl.ts
Enter fullscreen mode Exit fullscreen mode

You may need to set the PATH variable as seen below but with your own path to the deno executable in place of /Users/ajcwebdev.

export PATH="/Users/ajcwebdev/.deno/bin:$PATH"
Enter fullscreen mode Exit fullscreen mode

Create Project Files

All we need is a directory containing an index.js file.

mkdir ajcwebdev-oak
cd ajcwebdev-oak
touch index.js
echo '.DS_Store' > .gitignore
Enter fullscreen mode Exit fullscreen mode

Create Deno Server

Before diving into Oak, it's useful to see the underlying server code they are building upon. The Deno Standard Library has an http module with a basic hello world application. Save the following code in index.js:

// index.js

import { listenAndServe } from "https://deno.land/std@0.111.0/http/server.ts"

listenAndServe(
  ":8080",
  () => new Response("Hello from Deno on Localhost 8080")
)

console.log("Server running on localhost:8080")
Enter fullscreen mode Exit fullscreen mode

Run Deno Server

deno run \
  --allow-net index.js \
  --watch \
  --no-check
Enter fullscreen mode Exit fullscreen mode
Server running on localhost:8080
Enter fullscreen mode Exit fullscreen mode

Open localhost:8080.

01-hello-from-deno-localhost-8080

Create Oak Server

Oak is a third party module for Deno hosted on their deno.land/x service. To import one of these modules, use the following format for code URLs:

https://deno.land/x/IDENTIFIER@VERSION/FILE_PATH
Enter fullscreen mode Exit fullscreen mode

If you leave out the version it will default to the most recent version released for the module. The Deno docs recommend pinning to a specific version to avoid unexpected breaking changes. At the time of this writing, the current version of Oak is https://deno.land/x/oak@v9.0.1/mod.ts.

Application Class

The Application class coordinates managing the HTTP server, running middleware, and handling errors that occur when processing requests. Two methods are generally used:

  • Middleware is added via the .use() method.
  • The .listen() method starts the server and then processes requests with the registered middleware.

Once the server is open, before it starts processing requests, the application will fire a "listen" event, which can be listened for via the .addEventListener() method.

// index.js

import { Application } from "https://deno.land/x/oak@v9.0.1/mod.ts"

const app = new Application()

app.use((ctx) => {
  ctx.response.body = "Hello from Oak on Localhost 8080"
})

app.addEventListener('listen', () => {
  console.log(`Server running on localhost:8080`)
})

app.listen({ port: 8080 })
Enter fullscreen mode Exit fullscreen mode

The middleware is processed as a stack, where each middleware function can control the flow of the response. When the middleware is called, it is passed a context (ctx) and reference to the "next" method in the stack.

  • .response accesses the Response object to form the response sent back to the requestor.
  • The .body method returns a representation of the request body.

Return to localhost:8080.

02-hello-from-oak-localhost-8080

Respond with HTML

To respond with HTML instead of plain text, use .headers.set on ctx.response and set the Content-Type to text/html.

// index.js

import { Application } from "https://deno.land/x/oak@v9.0.1/mod.ts"

const app = new Application()

app.use((ctx) => {
  ctx.response.body = "<h2>Hello from Oak on Localhost 8080</h2>"
  ctx.response.headers.set("Content-Type", "text/html")
})

app.addEventListener('listen', () => {
  console.log(`Server running on localhost:8080`)
})

app.listen({ port: 8080 })
Enter fullscreen mode Exit fullscreen mode

Return to localhost:8080 to see the change.

03-hello-with-html-localhost-8080

Add Router

The Router class produces middleware which can be used with an Application to enable routing based on the pathname of the request.

// index.js

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

const router = new Router()
const app = new Application()

router.get("/", (ctx) => {
  ctx.response.body = "<h2>Hello from Router on Localhost 8080</h2>"
  ctx.response.headers.set("Content-Type", "text/html")
})

router.get("/about", (ctx) => {
  ctx.response.body = "<h2>This page tells you about stuff</h2>"
  ctx.response.headers.set("Content-Type", "text/html")
})

app.use(router.routes())
app.use(router.allowedMethods())

app.addEventListener('listen', () => {
  console.log(`Server running on localhost:8080`)
})

app.listen({ port: 8080 })
Enter fullscreen mode Exit fullscreen mode

Return to localhost:8080 to see the change.

04-hello-from-router-localhost-8080

Open localhost:8080/about to see the new about page.

05-about-page-localhost-8080

Deno Deploy

Deno Deploy is a distributed system that runs JavaScript, TypeScript, and WebAssembly at the edge, worldwide. It is a multi-tenant JavaScript engine running in 25 data centers across the world.

Run Oak Server with deployctl

You can run your script like it would run on Deno Deploy with deployctl. Include --no-check so TypeScript doesn't explode in a giant fireball of errors. You can also add the --watch flag to automatically restart the script when any modules in the module graph change.

deployctl run index.js --no-check --watch
Enter fullscreen mode Exit fullscreen mode

If you return to localhost:8080 there will be no change in the application.

Initialize GitHub Repository

git init
git add .
git commit -m "finally, another project named after a tree"
gh repo create ajcwebdev-oak
git push -u origin main
Enter fullscreen mode Exit fullscreen mode

Install the Deno Deploy GitHub App

We will be deploying this script from a URL on GitHub. Install the Deno Deploy GitHub App.

06-deno-deploy-github-app

Sign Up for Deno Deploy

Sign up for a Deno Deploy account and connect the account to your GitHub. You will be taken to a page for your projects.

07-deno-deploy-projects

Create Deno Deploy Project

Click "New Project" and enter a name for your project.

08-create-a-new-project

After creating your project you are given the option of using existing templates.

09-ajcwebdev-oak-project-page

Connect GitHub Repository

Since we will not be using a template, scroll down to connect the GitHub repository we just created.

10-deploy-from-github

Paste the raw GitHub URL for your function.

11-git-integration

Click "Link" and go to ajcwebdev-oak.deno.dev.

12-ajcwebdev-oak-deno-dev

You can use the project overview page to review your project's settings, add/remove domains, or view the deployment logs.

13-project-overview-page

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .