Use Oak as request handler in Deno

Mayank Choubey
Tech Tonic
4 min readAug 25, 2022

--

Introduction

Oak is a middleware framework for Deno’s native HTTP server, Deno Deploy and Node.js 16.5 and later. It also includes a middleware router. This middleware framework is inspired by Koa and middleware router inspired by @koa/router. The general use of Oak is to:

  • Create an application
  • Create a router middleware
  • Define routes
  • Start listening for HTTP requests
import { Application, Router } from "https://deno.land/x/oak/mod.ts";const app = new Application();
const router = new Router();
router
.get("/", (ctx) => ctx.response.redirect("/otherPath"))
.get("/otherPath", (ctx) => ctx.response.body = "Serving from other path");
app.use(router.routes());
app.use(router.allowedMethods());
app.listen({ port: 8082 });

The Oak framework receives the HTTP request (or the Request object), processes it, and then sends the HTTP response (through the Response object) back to the caller. In short, Oak controls the complete journey of the HTTP request. We never see the Request or Response objects.

In some cases, it may not be desirable to have Oak have so much control over request processing. There may be a need to use Oak as a simple request handler rather than a full controller that hides everything behind its APIs/middlewares/callbacks/etc. The prepared Response object should also be available to analyze by the application, and modified as needed.

Fortunately, Oak does come with a mode whereby it takes a Request object as input and returns a Response object. In other words, Oak acts as a request handler rather than a full controller. In this article, we’ll learn how to use Oak as a simple handler.

Oak’s handle API

Oak comes with a simple-to-use handle API that takes two inputs and returns one output:

  • Request: The first input is the web standard Request object
  • Conn: The second input is the Deno.Conn object that contains details about the underlying TCP connection
  • Response: The only output is the prepared Response object for successful handling or undefined if the handler encountered an error

The handle API is part of Oak’s Application object. Let’s look at a simple hello world example that is built on Deno’s native HTTP server. We’ll be using Deno’s standard library’s serve API to handle the HTTP requests and send responses back.

import { serve } from "https://deno.land/std/http/mod.ts";function reqHandler (req: Request) {
return new Response("Hello world"));
}
serve(reqHandler, { port: 8082 });

A simple test with curl:

> curl http://localhost:8082 -v
* Connected to localhost (127.0.0.1) port 8082 (#0)
> GET / HTTP/1.1
> Host: localhost:8082
> User-Agent: curl/7.79.1
> Accept: */*
>
< HTTP/1.1 200 OK
< content-type: text/plain;charset=UTF-8
< vary: Accept-Encoding
< content-length: 11
< date: Thu, 25 Aug 2022 20:46:30 GMT
<
Hello world

Now, we’ll update the above code to use Oak’s handle method:

import { Application } from "https://deno.land/x/oak/mod.ts";
import { serve } from "https://deno.land/std/http/mod.ts";
const app = new Application();
app.use((ctx) => ctx.response.body = "Hello world");
serve(async (req) => {
return await app.handle(req);
}, { port: 8082 });

Another simple test with curl:

> curl http://localhost:8082 -v
* Connected to localhost (127.0.0.1) port 8082 (#0)
> GET / HTTP/1.1
> Host: localhost:8082
> User-Agent: curl/7.79.1
> Accept: */*
>
< HTTP/1.1 200 OK
< content-type: text/plain; charset=UTF-8
< vary: Accept-Encoding
< content-length: 11
< date: Thu, 25 Aug 2022 20:56:03 GMT
<
Hello world

The Oak’s handle API takes the request through all the middlewares, runs them, and then returns a prepared response back. This simple case doesn’t add any value. The primary use to Oak is when routes are defined or other middlewares are used. Let’s rewrite the first application with handle API:

import { Application, Router } from "https://deno.land/x/oak/mod.ts";
import { serve } from "https://deno.land/std/http/mod.ts";
const app = new Application();
const router = new Router();
router
.get("/", (ctx) => ctx.response.redirect("/otherPath"))
.get("/otherPath", (ctx) => ctx.response.body = "Serving from other path");
app.use(router.routes());
app.use(router.allowedMethods());
serve(async (req) => {
return await app.handle(req);
}, { port: 8082 });

Here is a quick test using curl:

> curl http://localhost:8082 -v -L
* Connected to localhost (127.0.0.1) port 8082 (#0)
> GET / HTTP/1.1
> Host: localhost:8082
> User-Agent: curl/7.79.1
> Accept: */*
>
< HTTP/1.1 302 Found
< content-type: text/plain; charset=UTF-8
< location: /otherPath
< vary: Accept-Encoding
< transfer-encoding: chunked
< date: Thu, 25 Aug 2022 20:59:56 GMT
<
* Ignoring the response-body
* Connection #0 to host localhost left intact
* Issue another request to this URL: 'http://localhost:8082/otherPath'
* Re-using existing connection! (#0) with host localhost
* Connected to localhost (127.0.0.1) port 8082 (#0)
> GET /otherPath HTTP/1.1
> Host: localhost:8082
> User-Agent: curl/7.79.1
> Accept: */*
>
< HTTP/1.1 200 OK
< content-type: text/plain; charset=UTF-8
< vary: Accept-Encoding
< transfer-encoding: chunked
< date: Thu, 25 Aug 2022 20:59:56 GMT
<
* Connection #0 to host localhost left intact
Serving from other path

The Response objects for first and second request are:

1 ==> Response {
body: ReadableStream { locked: false },
bodyUsed: false,
headers: Headers { "content-type": "text/plain; charset=UTF-8", location: "/otherPath" },
ok: false,
redirected: false,
status: 302,
statusText: "Found",
url: ""
}
2 ==> Response {
body: ReadableStream { locked: false },
bodyUsed: false,
headers: Headers { "content-type": "text/plain; charset=UTF-8" },
ok: true,
redirected: false,
status: 200,
statusText: "OK",
url: ""
}

As mentioned above, Oak doesn’t control the lifecycle of the HTTP request. The application code opens the TCP server, listens for incoming connections, serves HTTP over it. Once the Request is available, it is handed over to Oak for further processing. Once Oak is done, it returns a Response object. The application can choose to send it as is, or change it, or drop it, or do whatever else is required. This adds tremendous flexibility in HTTP request handling along with the flexibility of using middlewares.

--

--