Oak
A middleware framework for handling HTTP with Deno, Node, Bun and Cloudflare Workers 🐿️ 🦕
Install / Use
/learn @oakserver/OakREADME
oak
A middleware framework for Deno's native HTTP server, Deno Deploy, Node.js 16.5 and later, Cloudflare Workers and Bun. It also includes a middleware router.
This middleware framework is inspired by Koa and middleware router inspired by @koa/router.
This README focuses on the mechanics of the oak APIs and is intended for those who are familiar with JavaScript middleware frameworks like Express and Koa as well as a decent understanding of Deno. If you aren't familiar with these, please check out documentation on oakserver.github.io/oak.
Also, check out our FAQs and the awesome-oak site of community resources.
[!NOTE] The examples in this README pull from
mainand are designed for Deno CLI or Deno Deploy, which may not make sense to do when you are looking to actually deploy a workload. You would want to "pin" to a particular version which is compatible with the version of Deno you are using and has a fixed set of APIs you would expect.https://deno.land/x/supports using git tags in the URL to direct you at a particular version. So to use version 13.0.0 of oak, you would want to importhttps://deno.land/x/oak@v13.0.0/mod.ts.
Usage
Deno CLI and Deno Deploy
oak is available on both deno.land/x and
JSR. To use from deno.land/x, import into a module:
import { Application } from "https://deno.land/x/oak/mod.ts";
To use from JSR, add it to your project:
deno add jsr:@oak/oak
Then import into a module:
import { Application } from "@oak/oak";
Node.js
oak is available for Node.js on both npm and JSR. To use from npm, install the package:
npm i @oakserver/oak
And then import into a module:
import { Application } from "@oakserver/oak";
To use from JSR, install the package:
npx jsr i @oak/oak
And then import into a module:
import { Application } from "@oak/oak/application";
[!NOTE] Send, websocket upgrades and serving over TLS/HTTPS are not currently supported.
In addition the Cloudflare Worker environment and execution context are not currently exposed to middleware.
Cloudflare Workers
oak is available for Cloudflare Workers on JSR. To use add the package to your Cloudflare Worker project:
npx jsr add @oak/oak
And then import into a module:
import { Application } from "@oak/oak/application";
Unlike other runtimes, the oak application doesn't listen for incoming requests, instead it handles worker fetch requests. A minimal example server would be:
import { Application } from "@oak/oak/application";
const app = new Application();
app.use((ctx) => {
ctx.response.body = "Hello CFW!";
});
export default { fetch: app.fetch };
[!NOTE] Send and websocket upgrades are not currently supported.
Bun
oak is available for Bun on JSR. To use install the package:
bunx jsr i @oak/oak
And then import into a module:
import { Application } from "@oak/oak/application";
[!NOTE] Send and websocket upgrades are not currently supported.
Application, middleware, and context
The Application class coordinates managing the HTTP server, running
middleware, and handling errors that occur when processing requests. Two of the
methods are generally used: .use() and .listen(). Middleware is added via
the .use() method and the .listen() method will start the server and start
processing requests with the registered middleware.
A basic usage, responding to every request with Hello World!:
import { Application } from "@oak/oak/application";
const app = new Application();
app.use((ctx) => {
ctx.response.body = "Hello World!";
});
await app.listen({ port: 8000 });
You would then run this script in Deno like:
> deno run --allow-net helloWorld.ts
For more information on running code under Deno, or information on how to install the Deno CLI, check out the Deno manual.
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 and reference to the "next" method in the stack.
A more complex example:
import { Application } from "@oak/oak/application";
const app = new Application();
// Logger
app.use(async (ctx, next) => {
await next();
const rt = ctx.response.headers.get("X-Response-Time");
console.log(`${ctx.request.method} ${ctx.request.url} - ${rt}`);
});
// Timing
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.response.headers.set("X-Response-Time", `${ms}ms`);
});
// Hello World!
app.use((ctx) => {
ctx.response.body = "Hello World!";
});
await app.listen({ port: 8000 });
To provide an HTTPS server, then the app.listen() options need to include the
options .secure option set to true and supply a .certFile and a .keyFile
options as well.
.handle() method
The .handle() method is used to process requests and receive responses without
having the application manage the server aspect. This though is advanced usage
and most users will want to use .listen().
The .handle() method accepts up to three arguments. The first being a
Request argument,
and the second being a Deno.Conn argument. The third optional argument is a
flag to indicate if the request was "secure" in the sense it originated from a
TLS connection to the remote client. The method resolved with a
Response object
or undefined if the ctx.respond === true.
An example:
import { Application } from "@oak/oak/application";
const app = new Application();
app.use((ctx) => {
ctx.response.body = "Hello World!";
});
Deno.serve(async (request, info) => {
const res = await app.handle(request, info.remoteAddr);
return res ?? Response.error();
});
An instance of application has some properties as well:
-
contextState- Determines the method used to create state for a new context. A value of"clone"will set the state as a clone of the app state. A value of"prototype"means the app's state will be used as the prototype of the context's state. A value of"alias"means that the application's state and the context's state will be a reference to the same object. A value of"empty"will initialize the context's state with an empty object. -
.jsonBodyReplacer- An optional replacer function which will be applied to JSON bodies when forming a response. -
.jsonBodyReviver- An optional reviver function which will be applied when reading JSON bodies in a request. -
.keysKeys to be used when signing and verifying cookies. The value can be set to an array of keys, and instance of
KeyStack, or an object which provides the same interface asKeyStack(e.g. an instance of keygrip). If just the keys are passed, oak will manage the keys viaKeyStackwhich allows easy key rotation without requiring re-signing of data values. -
.proxyThis defaults to
false, but can be set via theApplicationconstructor options. This is intended to indicate the application is behind a proxy and will useX-Forwarded-Proto,X-Forwarded-Host, andX-Forwarded-Forwhen processing the request, which should provide more accurate information about the request. -
.stateA record of application state, which can be strongly typed by specifying a generic argument when constructing an
Application(), or inferred by passing a state object (e.g.Application({ state })).
Context
The context passed to middleware has several properties:
-
.appA reference to the
Applicationthat is invoking this middleware. -
.cookiesThe
Cookiesinstance for this context which allows you to read and set cookies. -
.requestThe
Requestobject which contains details about the request. -
.respondDetermines if when middleware finishes processing, the application should send the
.responseto the client. Iftruethe response will be sent, and iffalsethe response will not be sent. The default istruebut certain methods, like.upgrade()and.sendEvents()will set this tofalse. -
.responseThe
Responseobject which will be used to form the response sent back to the requestor. -
.socketThis will be
undefinedif the connection has not been upgraded to a web socket. If the connection has been upgraded, the.socketinterface will be set. -
.stateA record of application state, which can be strongly typed by specifying a generic argument when constructing an
Application(), or inferred by passing a state object (e.g.Application({ state })).
The context
