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 main and 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 import https://deno.land/x/[email protected]/mod.ts.
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, import into a module:
import { Application } from "jsr:@oak/oak";Or use the Deno CLI to add it to your project:
deno add jsr:@oak/oak
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.
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.
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.
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 "jsr:@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 "jsr:@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.
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 "jsr:@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 })).
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 passed to middleware has some methods:
-
.assert()Makes an assertion, which if not true, throws an
HTTPError, which subclass is identified by the second argument, with the message being the third argument. -
.send()Stream a file to the requesting client. See Static content below for more information.
-
.sendEvents()Convert the current connection into a server-sent event response and return a
ServerSentEventTargetwhere messages and events can be streamed to the client. This will set.respondtofalse. -
.throw()Throws an
HTTPError, which subclass is identified by the first argument, with the message being passed as the second. -
.upgrade()Attempt to upgrade the connection to a web socket connection, and return a
WebSocketinterface. Previous version of oak, this would be aPromiseresolving with astd/wsweb socket.
Unlike other middleware frameworks, context does not have a significant amount
of aliases. The information about the request is only located in .request and
the information about the response is only located in .response.
The context.cookies allows access to the values of cookies in the request, and
allows cookies to be set in the response. It automatically secures cookies if
the .keys property is set on the application. Because .cookies uses the web
crypto APIs to sign and validate cookies, and those APIs work in an asynchronous
way, the cookie APIs work in an asynchronous way. It has several methods:
-
.get(key: string, options?: CookieGetOptions): Promise<string | undefined>Attempts to retrieve the cookie out of the request and returns the value of the cookie based on the key. If the applications
.keysis set, then the cookie will be verified against a signed version of the cookie. If the cookie is valid, the promise will resolve with the value. If it is invalid, the cookie signature will be set to deleted on the response. If the cookie was not signed by the current key, it will be resigned and added to the response. -
.set(key: string, value: string, options?: CookieSetDeleteOptions): Promise<void>Will set a cookie in the response based on the provided key, value and any options. If the applications
.keysis set, then the cookie will be signed and the signature added to the response. As the keys are signed asynchronously, awaiting the.set()method is advised.
The context.request contains information about the request. It contains
several properties:
-
.bodyAn object which provides access to the body of the request. See below for details about the request body API.
-
.hasBodySet to
trueif the request might have a body, orfalseif it does not. It does not validate if the body is supported by the built in body parser though.[!WARNING] This is an unreliable API. In HTTP/2 in many situations you cannot determine if a request has a body or not unless you attempt to read the body, due to the streaming nature of HTTP/2. As of Deno 1.16.1, for HTTP/1.1, Deno also reflects that behavior. The only reliable way to determine if a request has a body or not is to attempt to read the body.
It is best to determine if a body might be meaningful to you with a given method, and then attempt to read and process the body if it is meaningful in a given context. For example
GETandHEADshould never have a body, but methods likeDELETEandOPTIONSmight have a body and should be have their body conditionally processed if it is meaningful to your application. -
.headersThe headers for the request, an instance of
Headers. -
.methodA string that represents the HTTP method for the request.
-
.originalRequestDEPRECATED this will be removed in a future release of oak.
The "raw"
NativeServerrequest, which is an abstraction over the DOMRequestobject..originalRequest.requestis the DOMRequestinstance that is being processed. Users should generally avoid using these. -
.secureA shortcut for
.protocol, returningtrueif HTTPS otherwisefalse. -
.sourceWhen running under Deno,
.sourcewill be set to the source web standardRequest. When running under NodeJS, this will beundefined. -
.urlAn instance of
URLwhich is based on the full URL for the request. This is in place of having parts of the URL exposed on the rest of the request object.
And several methods:
-
.accepts(...types: string[])Negotiates the content type supported by the request for the response. If no content types are passed, the method returns a prioritized array of accepted content types. If content types are passed, the best negotiated content type is returned. If no content type match
undefinedis returned. -
.acceptsEncodings(...encodings: string[])Negotiates the content encoding supported by the request for the response. If no encodings are passed, the method returns a prioritized array of accepted encodings. If encodings are passed, the best negotiated encoding is returned. If no encodings match
undefinedis returned. -
.acceptsLanguages(...languages: string[])Negotiates the language the client is able to understand. Where a locale variant takes preference. If no encodings are passed, the method returns a prioritized array of understood languages. If languages are passed, the best negotiated language is returned. If no languages match
undefinedis returned.
Important
This API changed significantly in oak v13 and later. The previous API had
grown organically since oak was created in 2018 and didn't represent any other
common API. The API introduced in v13 aligns better to the Fetch API's
Request way of dealing with the body, and should be more familiar to
developers coming to oak for the first time.
The API for the oak request .body is inspired by the Fetch API's Request but
with some add functionality. The context's request.body is an instance of an
object which provides several properties:
-
.hasSet to
trueif the request might have a body, orfalseif it does not. It does not validate if the body is supported by the built in body parser though.[!IMPORTANT] This is an unreliable API. In HTTP/2 in many situations you cannot determine if a request has a body or not unless you attempt to read the body, due to the streaming nature of HTTP/2. As of Deno 1.16.1, for HTTP/1.1, Deno also reflects that behavior. The only reliable way to determine if a request has a body or not is to attempt to read the body.
It is best to determine if a body might be meaningful to you with a given method, and then attempt to read and process the body if it is meaningful in a given context. For example
GETandHEADshould never have a body, but methods likeDELETEandOPTIONSmight have a body and should be have their body conditionally processed if it is meaningful to your application. -
.streamA
ReadableStream<Uint8Array>that will allow reading of the body inUint8Arraychunks. This is akin the.bodyproperty in a Fetch APIRequest. -
.usedSet to
trueif the body has been used, otherwise set tofalse.
It also has several methods:
-
arrayBuffer()Resolves with an
ArrayBufferthat contains the contents of the body, if any. Suitable for reading/handling binary data. -
blob()Resolves with a
Blobthat contains the contents of the body. Suitable for reading/handling binary data. -
form()Resolves with a
URLSearchParamswhich has been decoded from the contents of a body. This is appropriate for a body with a content type ofapplication/x-www-form-urlencoded. -
formData()Resolves with a
FormDatainstance which has been decoded from the contents of a body. This is appropriate for a body with a content type ofmultipart/form-data. -
json()Resolves with the data from the body parsed as JSON. If a
jsonBodyReviverhas been specified in the application, it will be used when parsing the JSON. -
text()Resolves with a string that represents the contents of the body.
-
type()Attempts to provide guidance of how the body is encoded which can be used to determine what method to use to decode the body. The method returns a string that represents the interpreted body type:
Value Description "binary"The body has a content type that indicates binary data and the .arrayBuffer(),.blob()or.streamshould be used to read the body."form"The body is encoded as form data and .form()should be used to read the body."form-data"The body is encoded as a multi-part form and .formData()should be used to read the body."json"The body is encoded as JSON data and .json()should be used to read the body."text"The body is encoded as text and .text()should be used to read the body."unknown"Either there is no body or it was not possible to determine the body type based on the content type. The
.type()method also takes an optional argument of custom media types that will be used when attempting to determine the type of the body. These are then incorporated into the default media types. The value is an object where the key is one ofbinary,form,form-data,json, ortextand the value is the appropriate media type in a format compatible with the type-is format.
The context.response contains information about the response which will be
sent back to the requestor. It contains several properties:
-
.bodyThe body of the response, which can often be handled by the automatic response body handling documented below.
-
.headersA
Headersinstance which contains the headers for the response. -
.statusAn HTTP
Statuscode that will be sent back with the response. If this is not set before responding, oak will default to200 OKif there is a.body, otherwise404 Not Found. -
.typeA media type or extension to set the
Content-Typeheader for the response. For example, you can providetxtortext/plainto describe the body.
And several methods:
-
.redirect(url?: string | URL | REDIRECT_BACK, alt?: string | URL)A method to simplify redirecting the response to another URL. It will set the
Locationheader to the suppliedurland the status to302 Found(unless the status is already a3XXstatus). The use of symbolREDIRECT_BACKas theurlindicates that theRefererheader in the request should be used as the direction, with thealtbeing the alternative location if theRefereris not set. If neither thealtnor theRefererare set, the redirect will occur to/. A basic HTML (if the requestor supports it) or a text body will be set explaining they are being redirected. -
.toDomResponse()This converts the information oak understands about the response to the Fetch API
Response. This will finalize the response, resulting in any further attempt to modify the response to throw. This is intended to be used internally within oak to be able to respond to requests. -
.with(response: Response)and.with(body?: BodyInit, init?: ResponseInit)This sets the response to a web standard
Response. Note that this will ignore/override any other information set on the response by other middleware including things like headers or cookies to be set.
When the response Content-Type is not set in the headers of the .response,
oak will automatically try to determine the appropriate Content-Type. First it
will look at .response.type. If assigned, it will try to resolve the
appropriate media type based on treating the value of .type as either the
media type, or resolving the media type based on an extension. For example if
.type was set to "html", then the Content-Type will be set to
"text/html".
If .type is not set with a value, then oak will inspect the value of
.response.body. If the value is a string, then oak will check to see if the
string looks like HTML, if so, Content-Type will be set to text/html
otherwise it will be set to text/plain. If the value is an object, other than
a Uint8Array, a Deno.Reader, or null, the object will be passed to
JSON.stringify() and the Content-Type will be set to application/json.
If the type of body is a number, bigint or symbol, it will be coerced to a string and treated as text.
If the value of body is a function, the function will be called with no arguments. If the return value of the function is promise like, that will be await, and the resolved value will be processed as above. If the value is not promise like, it will be processed as above.
The application method .listen() is used to open the server, start listening
for requests, and processing the registered middleware for each request. This
method returns a promise when the server closes.
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. For example:
import { Application } from "jsr:@oak/oak/application";
const app = new Application();
app.addEventListener("listen", ({ hostname, port, secure }) => {
console.log(
`Listening on: ${secure ? "https://" : "http://"}${
hostname ?? "localhost"
}:${port}`,
);
});
// register some middleware
await app.listen({ port: 80 });If you want to close the application, the application supports the option of an abort signal. Here is an example of using the signal:
import { Application } from "jsr:@oak/oak/application";
const app = new Application();
const controller = new AbortController();
const { signal } = controller;
// Add some middleware using `app.use()`
const listenPromise = app.listen({ port: 8000, signal });
// In order to close the server...
controller.abort();
// Listen will stop listening for requests and the promise will resolve...
await listenPromise;
// and you can do something after the close to shutdownMiddleware can be used to handle other errors with middleware. Awaiting other middleware to execute while trapping errors works. So if you had an error handling middleware that provides a well managed response to errors would work like this:
import { Application } from "jsr:@oak/oak/application";
import { isHttpError } from "jsr:@oak/commons/http_errors";
import { Status } from "jsr:@oak/commons/status";
const app = new Application();
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
if (isHttpError(err)) {
switch (err.status) {
case Status.NotFound:
// handle NotFound
break;
default:
// handle other statuses
}
} else {
// rethrow if you can't handle the error
throw err;
}
}
});Uncaught middleware exceptions will be caught by the application. Application
extends the global EventTarget in Deno, and when uncaught errors occur in the
middleware or sending of responses, an EventError will be dispatched to the
application. To listen for these errors, you would add an event handler to the
application instance:
import { Application } from "jsr:@oak/oak/application";
const app = new Application();
app.addEventListener("error", (evt) => {
// Will log the thrown error to the console.
console.log(evt.error);
});
app.use((ctx) => {
// Will throw a 500 on every request.
ctx.throw(500);
});
await app.listen({ port: 80 });The Router class produces middleware which can be used with an Application
to enable routing based on the pathname of the request.
The following example serves up a RESTful service of a map of books, where
http://localhost:8000/book/ will return an array of books and
http://localhost:8000/book/1 would return the book with ID "1":
import { Application } from "jsr:@oak/oak/application";
import { Router } from "jsr:@oak/oak/router";
const books = new Map<string, any>();
books.set("1", {
id: "1",
title: "The Hound of the Baskervilles",
author: "Conan Doyle, Arthur",
});
const router = new Router();
router
.get("/", (context) => {
context.response.body = "Hello world!";
})
.get("/book", (context) => {
context.response.body = Array.from(books.values());
})
.get("/book/:id", (context) => {
if (books.has(context?.params?.id)) {
context.response.body = books.get(context.params.id);
}
});
const app = new Application();
app.use(router.routes());
app.use(router.allowedMethods());
await app.listen({ port: 8000 });A route passed is converted to a regular expression using
path-to-regexp, which means
parameters expressed in the pattern will be converted. path-to-regexp has
advanced usage which can create complex patterns which can be used for matching.
Check out the
documentation for that library
if you have advanced use cases.
In most cases, the type of context.params is automatically inferred from the
path template string through typescript magic. In more complex scenarios this
might not yield the correct result however. In that case you can override the
type with router.get<RouteParams>, where RouteParams is the explicit type
for context.params.
Nesting routers is supported. The following example responds to
http://localhost:8000/forums/oak/posts and
http://localhost:8000/forums/oak/posts/nested-routers.
import { Application } from "jsr:@oak/oak/application";
import { Router } from "jsr:@oak/oak/router";
const posts = new Router()
.get("/", (ctx) => {
ctx.response.body = `Forum: ${ctx.params.forumId}`;
})
.get("/:postId", (ctx) => {
ctx.response.body =
`Forum: ${ctx.params.forumId}, Post: ${ctx.params.postId}`;
});
const forums = new Router().use(
"/forums/:forumId/posts",
posts.routes(),
posts.allowedMethods(),
);
await new Application().use(forums.routes()).listen({ port: 8000 });The function send() is designed to serve static content as part of a
middleware function. In the most straight forward usage, a root is provided and
requests provided to the function are fulfilled with files from the local file
system relative to the root from the requested path.
A basic usage would look something like this:
import { Application } from "jsr:@oak/oak/application";
const app = new Application();
app.use(async (context, next) => {
try {
await context.send({
root: `${Deno.cwd()}/examples/static`,
index: "index.html",
});
} catch {
await next();
}
});
await app.listen({ port: 8000 });send() automatically supports features like providing ETag and
Last-Modified headers in the response as well as processing If-None-Match
and If-Modified-Since headers in the request. This means when serving up
static content, clients will be able to rely upon their cached versions of
assets instead of re-downloading them.
The send() method automatically supports generating an ETag header for
static assets. The header allows the client to determine if it needs to
re-download an asset or not, but it can be useful to calculate ETags for other
scenarios.
There is a middleware function that assesses the context.reponse.body and
determines if it can create an ETag header for that body type, and if so sets
the ETag header on the response. Basic usage would look something like this:
import { Application } from "jsr:@oak/oak/application";
import { factory } from "jsr:@oak/oak/etag";
const app = new Application();
app.use(factory());
// ... other middleware for the applicationThere is also a function which retrieves an entity for a given context based on what it logical to read into memory which can be passed to the etag calculate that is part of the Deno std library:
import { Application } from "jsr:@oak/oak/application";
import { getEntity } from "jsr:@oak/oak/etag";
import { calculate } from "jsr:@std/http/etag";
const app = new Application();
// The context.response.body has already been set...
app.use(async (ctx) => {
const entity = await getEntity(ctx);
if (entity) {
const etag = await calculate(entity);
}
});If you are migrating from Deno.serve() or adapting code that is designed for
the web standard Fetch API Request and Response, there are a couple features
of oak to assist.
When running under Deno, this will be set to a Fetch API Request, giving
direct access to the original request.
This method will accept a Fetch API Response or create a new response based
on the provided BodyInit and ResponseInit. This will also finalize the
response and ignores anything that may have been set on the oak .response.
These two middleware generators can be used to adapt code that operates more
like the Deno.serve() in that it provides a Fetch API Request and expects
the handler to resolve with a Fetch API Response.
An example of using serve() with Application.prototype.use():
import { Application } from "jsr:@oak/oak/application";
import { serve } from "jsr:@oak/oak/serve";
const app = new Application();
app.use(serve((req, ctx) => {
console.log(req.url);
return new Response("Hello world!");
}));
app.listen();And a similar solution works with route() where the context contains the
information about the router, like the params:
import { Application } from "jsr:@oak/oak/application";
import { Router } from "jsr:@oak/oak/router";
import { route } from "jsr:@oak/oak/serve";
const app = new Application;
const router = new Router();
router.get("/books/:id", route((req, ctx) => {
console.log(ctx.params.id);
return Response.json({ title: "hello world", id: ctx.params.id });
}));
app.use(router.routes());
app.listen();The mod.ts exports an object named testing which contains some utilities for
testing oak middleware you might create. See the
Testing with oak for more
information.
There are several modules that are directly adapted from other modules. They have preserved their individual licenses and copyrights. All of the modules, including those directly adapted are licensed under the MIT License.
All additional work is copyright 2018 - 2025 the oak authors. All rights reserved.