diff --git a/README.md b/README.md
index eba8724..c2390ad 100644
--- a/README.md
+++ b/README.md
@@ -175,6 +175,11 @@ 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.
+Middleware can also be an object as long as it has a `handleRequest` method.
+Middleware objects can optionally have an `init` function which will be called
+when the application starts listening. This makes middleware objects ideal for
+encapsulation or delayed initialization.
+
A more complex example:
```ts
@@ -197,6 +202,25 @@ app.use(async (ctx, next) => {
ctx.response.headers.set("X-Response-Time", `${ms}ms`);
});
+// Counter
+class CountingMiddleware implements MiddlewareObject {
+ #id = 0;
+ #counter = 0;
+
+ init() {
+ const array = new Uint32Array(1);
+ crypto.getRandomValues(array);
+ this.#id = array[0];
+ }
+
+ handleRequest(ctx: Context, next: Next) {
+ ctx.response.headers.set("X-Response-Count", String(this.#counter++));
+ ctx.response.headers.set("X-Response-Counter-ID", String(this.#id));
+ return next();
+ }
+}
+app.use(new CountingMiddleware());
+
// Hello World!
app.use((ctx) => {
ctx.response.body = "Hello World!";
@@ -758,6 +782,9 @@ 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.
+Like regular `Middleware`, router middleware can be an object as long as the
+`handleRequest` method is implemented.
+
### Basic usage
The following example serves up a _RESTful_ service of a map of books, where
diff --git a/examples/routingServer.ts b/examples/routingServer.ts
index afac56a..66b9db0 100644
--- a/examples/routingServer.ts
+++ b/examples/routingServer.ts
@@ -34,6 +34,32 @@ function notFound(context: Context) {
`
404 - Not Found
Path ${context.request.url}
not found.`;
}
+class OrdersObject {
+ #orders = new Set();
+
+ handleRequest(context: RouterContext<"/orders">) {
+ if (context.params["order"]) {
+ if (this.#orders.has(context.params["order"])) {
+ context.response.body = `${
+ context.params["order"]
+ } is already ordered.`;
+ } else {
+ this.#orders.add(context.params["order"]);
+ context.response.body = `Ordered ${context.params["order"]}.`;
+ }
+ } else {
+ const orders = Array.from(this.#orders.values());
+ if (orders.length > 0) {
+ context.response.body = `Orders: ${orders.join(", ")}`;
+ } else {
+ context.response.body = "No orders so far.";
+ }
+ }
+ }
+}
+
+const orders = new OrdersObject();
+
const router = new Router();
router
.get("/", (context) => {
@@ -78,7 +104,9 @@ router
} else {
return notFound(context);
}
- });
+ })
+ .get("/orders", orders)
+ .get("/orders/:order", orders);
const app = new Application();
diff --git a/mod.ts b/mod.ts
index 4c22e07..6089521 100644
--- a/mod.ts
+++ b/mod.ts
@@ -123,15 +123,17 @@ export {
} from "./middleware.ts";
export { Request } from "./request.ts";
export { REDIRECT_BACK, Response } from "./response.ts";
-export {
- type Route,
- type RouteParams,
- Router,
- type RouterAllowedMethodsOptions,
- type RouterContext,
- type RouterMiddleware,
- type RouterOptions,
- type RouterParamMiddleware,
+export { Router } from "./router.ts";
+export type {
+ Route,
+ RouteParams,
+ RouterAllowedMethodsOptions,
+ RouterContext,
+ RouterMiddleware,
+ RouterMiddlewareObject,
+ RouterMiddlewareOrMiddlewareObject,
+ RouterOptions,
+ RouterParamMiddleware,
} from "./router.ts";
export { send, type SendOptions } from "./send.ts";
/** Utilities for making testing oak servers easier. */
diff --git a/router.ts b/router.ts
index db338fd..c79e3da 100644
--- a/router.ts
+++ b/router.ts
@@ -66,7 +66,7 @@ import {
Status,
type TokensToRegexpOptions,
} from "./deps.ts";
-import { compose, type Middleware } from "./middleware.ts";
+import { compose, type Middleware, type Next } from "./middleware.ts";
import { decodeComponent } from "./utils/decode_component.ts";
interface Matches {
@@ -105,7 +105,7 @@ export interface Route<
methods: HTTPMethods[];
/** The middleware that will be applied to this route. */
- middleware: RouterMiddleware[];
+ middleware: RouterMiddlewareOrMiddlewareObject[];
/** An optional name for the route. */
name?: string;
@@ -161,7 +161,7 @@ export interface RouterMiddleware<
// deno-lint-ignore no-explicit-any
S extends State = Record,
> {
- (context: RouterContext, next: () => Promise):
+ (context: RouterContext, next: Next):
| Promise
| unknown;
/** For route parameter middleware, the `param` key for this parameter will
@@ -171,6 +171,29 @@ export interface RouterMiddleware<
router?: Router;
}
+export interface RouterMiddlewareObject<
+ R extends string,
+ P extends RouteParams = RouteParams,
+ // deno-lint-ignore no-explicit-any
+ S extends State = Record,
+> {
+ init?: () => Promise | unknown;
+ handleRequest(
+ context: RouterContext,
+ next: Next,
+ ): Promise | unknown;
+ param?: keyof P;
+ // deno-lint-ignore no-explicit-any
+ router?: Router;
+}
+
+export type RouterMiddlewareOrMiddlewareObject<
+ R extends string,
+ P extends RouteParams = RouteParams,
+ // deno-lint-ignore no-explicit-any
+ S extends State = Record,
+> = RouterMiddleware | RouterMiddlewareObject;
+
/** Options which can be specified when creating a new instance of a
* {@linkcode Router}. */
export interface RouterOptions {
@@ -303,12 +326,14 @@ export class Layer<
methods: HTTPMethods[];
name?: string;
path: string;
- stack: RouterMiddleware[];
+ stack: RouterMiddlewareOrMiddlewareObject[];
constructor(
path: string,
methods: HTTPMethods[],
- middleware: RouterMiddleware | RouterMiddleware[],
+ middleware:
+ | RouterMiddlewareOrMiddlewareObject
+ | RouterMiddlewareOrMiddlewareObject[],
{ name, ...opts }: LayerOptions = {},
) {
this.#opts = opts;
@@ -378,7 +403,7 @@ export class Layer<
): Promise | unknown {
const p = ctx.params[param];
assert(p);
- return fn.call(this, p, ctx, next);
+ return fn.call(this, p!, ctx, next);
};
middleware.param = param;
@@ -525,7 +550,7 @@ export class Router<
#register(
path: string | string[],
- middlewares: RouterMiddleware[],
+ middlewares: RouterMiddlewareOrMiddlewareObject[],
methods: HTTPMethods[],
options: RegisterOptions = {},
): void {
@@ -536,7 +561,7 @@ export class Router<
return;
}
- let layerMiddlewares: RouterMiddleware[] = [];
+ let layerMiddlewares: RouterMiddlewareOrMiddlewareObject[] = [];
for (const middleware of middlewares) {
if (!middleware.router) {
layerMiddlewares.push(middleware);
@@ -572,7 +597,7 @@ export class Router<
#addLayer(
path: string,
- middlewares: RouterMiddleware[],
+ middlewares: RouterMiddlewareOrMiddlewareObject[],
methods: HTTPMethods[],
options: LayerOptions = {},
) {
@@ -612,8 +637,8 @@ export class Router<
#useVerb(
nameOrPath: string,
- pathOrMiddleware: string | RouterMiddleware,
- middleware: RouterMiddleware[],
+ pathOrMiddleware: string | RouterMiddlewareOrMiddlewareObject,
+ middleware: RouterMiddlewareOrMiddlewareObject[],
methods: HTTPMethods[],
): void {
let name: string | undefined = undefined;
@@ -660,8 +685,8 @@ export class Router<
methods: HTTPMethods[] | HTTPMethods,
name: string,
path: R,
- middleware: RouterMiddleware,
- ...middlewares: RouterMiddleware[]
+ middleware: RouterMiddlewareOrMiddlewareObject,
+ ...middlewares: RouterMiddlewareOrMiddlewareObject[]
): Router;
/** Register middleware for the specified routes when the specified methods is
* requested. */
@@ -672,8 +697,8 @@ export class Router<
>(
methods: HTTPMethods[] | HTTPMethods,
path: R,
- middleware: RouterMiddleware,
- ...middlewares: RouterMiddleware[]
+ middleware: RouterMiddlewareOrMiddlewareObject,
+ ...middlewares: RouterMiddlewareOrMiddlewareObject[]
): Router;
/** Register middleware for the specified routes when the specified methods
* are requested with explicit path parameters. */
@@ -683,8 +708,8 @@ export class Router<
>(
methods: HTTPMethods[] | HTTPMethods,
nameOrPath: string,
- pathOrMiddleware: string | RouterMiddleware,
- ...middleware: RouterMiddleware[]
+ pathOrMiddleware: string | RouterMiddlewareOrMiddlewareObject,
+ ...middleware: RouterMiddlewareOrMiddlewareObject[]
): Router;
add<
P extends RouteParams = RouteParams,
@@ -692,13 +717,13 @@ export class Router<
>(
methods: HTTPMethods[] | HTTPMethods,
nameOrPath: string,
- pathOrMiddleware: string | RouterMiddleware,
- ...middleware: RouterMiddleware[]
+ pathOrMiddleware: string | RouterMiddlewareOrMiddlewareObject,
+ ...middleware: RouterMiddlewareOrMiddlewareObject[]
): Router {
this.#useVerb(
nameOrPath,
- pathOrMiddleware as (string | RouterMiddleware),
- middleware as RouterMiddleware[],
+ pathOrMiddleware as (string | RouterMiddlewareOrMiddlewareObject),
+ middleware as RouterMiddlewareOrMiddlewareObject[],
typeof methods === "string" ? [methods] : methods,
);
return this;
@@ -713,8 +738,8 @@ export class Router<
>(
name: string,
path: R,
- middleware: RouterMiddleware,
- ...middlewares: RouterMiddleware[]
+ middleware: RouterMiddlewareOrMiddlewareObject,
+ ...middlewares: RouterMiddlewareOrMiddlewareObject[]
): Router;
/** Register middleware for the specified routes when the `DELETE`,
* `GET`, `POST`, or `PUT` method is requested. */
@@ -724,8 +749,8 @@ export class Router<
S extends State = RS,
>(
path: R,
- middleware: RouterMiddleware,
- ...middlewares: RouterMiddleware[]
+ middleware: RouterMiddlewareOrMiddlewareObject,
+ ...middlewares: RouterMiddlewareOrMiddlewareObject[]
): Router;
/** Register middleware for the specified routes when the `DELETE`,
* `GET`, `POST`, or `PUT` method is requested with explicit path parameters.
@@ -735,21 +760,21 @@ export class Router<
S extends State = RS,
>(
nameOrPath: string,
- pathOrMiddleware: string | RouterMiddleware,
- ...middleware: RouterMiddleware[]
+ pathOrMiddleware: string | RouterMiddlewareOrMiddlewareObject,
+ ...middleware: RouterMiddlewareOrMiddlewareObject[]
): Router;
all<
P extends RouteParams = RouteParams,
S extends State = RS,
>(
nameOrPath: string,
- pathOrMiddleware: string | RouterMiddleware,
- ...middleware: RouterMiddleware[]
+ pathOrMiddleware: string | RouterMiddlewareOrMiddlewareObject,
+ ...middleware: RouterMiddlewareOrMiddlewareObject[]
): Router {
this.#useVerb(
nameOrPath,
- pathOrMiddleware as (string | RouterMiddleware),
- middleware as RouterMiddleware[],
+ pathOrMiddleware as (string | RouterMiddlewareOrMiddlewareObject),
+ middleware as RouterMiddlewareOrMiddlewareObject[],
this.#methods.filter((method) => method !== "OPTIONS"),
);
return this;
@@ -782,7 +807,7 @@ export class Router<
if (!ctx.response.status || ctx.response.status === Status.NotFound) {
assert(ctx.matched);
const allowed = new Set();
- for (const route of ctx.matched) {
+ for (const route of ctx.matched!) {
for (const method of route.methods) {
allowed.add(method);
}
@@ -828,8 +853,8 @@ export class Router<
>(
name: string,
path: R,
- middleware: RouterMiddleware,
- ...middlewares: RouterMiddleware[]
+ middleware: RouterMiddlewareOrMiddlewareObject,
+ ...middlewares: RouterMiddlewareOrMiddlewareObject[]
): Router;
/** Register middleware for the specified routes when the `DELETE`,
* method is requested. */
@@ -839,8 +864,8 @@ export class Router<
S extends State = RS,
>(
path: R,
- middleware: RouterMiddleware,
- ...middlewares: RouterMiddleware[]
+ middleware: RouterMiddlewareOrMiddlewareObject,
+ ...middlewares: RouterMiddlewareOrMiddlewareObject[]
): Router;
/** Register middleware for the specified routes when the `DELETE`,
* method is requested with explicit path parameters. */
@@ -849,21 +874,21 @@ export class Router<
S extends State = RS,
>(
nameOrPath: string,
- pathOrMiddleware: string | RouterMiddleware,
- ...middleware: RouterMiddleware[]
+ pathOrMiddleware: string | RouterMiddlewareOrMiddlewareObject,
+ ...middleware: RouterMiddlewareOrMiddlewareObject[]
): Router;
delete<
P extends RouteParams = RouteParams,
S extends State = RS,
>(
nameOrPath: string,
- pathOrMiddleware: string | RouterMiddleware,
- ...middleware: RouterMiddleware[]
+ pathOrMiddleware: string | RouterMiddlewareOrMiddlewareObject,
+ ...middleware: RouterMiddlewareOrMiddlewareObject[]
): Router {
this.#useVerb(
nameOrPath,
- pathOrMiddleware as (string | RouterMiddleware),
- middleware as RouterMiddleware[],
+ pathOrMiddleware as (string | RouterMiddlewareOrMiddlewareObject),
+ middleware as RouterMiddlewareOrMiddlewareObject[],
["DELETE"],
);
return this;
@@ -905,8 +930,8 @@ export class Router<
>(
name: string,
path: R,
- middleware: RouterMiddleware,
- ...middlewares: RouterMiddleware[]
+ middleware: RouterMiddlewareOrMiddlewareObject,
+ ...middlewares: RouterMiddlewareOrMiddlewareObject[]
): Router;
/** Register middleware for the specified routes when the `GET`,
* method is requested. */
@@ -916,8 +941,8 @@ export class Router<
S extends State = RS,
>(
path: R,
- middleware: RouterMiddleware,
- ...middlewares: RouterMiddleware[]
+ middleware: RouterMiddlewareOrMiddlewareObject,
+ ...middlewares: RouterMiddlewareOrMiddlewareObject[]
): Router;
/** Register middleware for the specified routes when the `GET`,
* method is requested with explicit path parameters. */
@@ -926,21 +951,21 @@ export class Router<
S extends State = RS,
>(
nameOrPath: string,
- pathOrMiddleware: string | RouterMiddleware,
- ...middleware: RouterMiddleware[]
+ pathOrMiddleware: string | RouterMiddlewareOrMiddlewareObject,
+ ...middleware: RouterMiddlewareOrMiddlewareObject[]
): Router;
get<
P extends RouteParams = RouteParams,
S extends State = RS,
>(
nameOrPath: string,
- pathOrMiddleware: string | RouterMiddleware,
- ...middleware: RouterMiddleware[]
+ pathOrMiddleware: string | RouterMiddlewareOrMiddlewareObject,
+ ...middleware: RouterMiddlewareOrMiddlewareObject[]
): Router {
this.#useVerb(
nameOrPath,
- pathOrMiddleware as (string | RouterMiddleware),
- middleware as RouterMiddleware[],
+ pathOrMiddleware as (string | RouterMiddlewareOrMiddlewareObject),
+ middleware as RouterMiddlewareOrMiddlewareObject[],
["GET"],
);
return this;
@@ -955,8 +980,8 @@ export class Router<
>(
name: string,
path: R,
- middleware: RouterMiddleware,
- ...middlewares: RouterMiddleware[]
+ middleware: RouterMiddlewareOrMiddlewareObject,
+ ...middlewares: RouterMiddlewareOrMiddlewareObject[]
): Router;
/** Register middleware for the specified routes when the `HEAD`,
* method is requested. */
@@ -966,8 +991,8 @@ export class Router<
S extends State = RS,
>(
path: R,
- middleware: RouterMiddleware,
- ...middlewares: RouterMiddleware[]
+ middleware: RouterMiddlewareOrMiddlewareObject,
+ ...middlewares: RouterMiddlewareOrMiddlewareObject[]
): Router;
/** Register middleware for the specified routes when the `HEAD`,
* method is requested with explicit path parameters. */
@@ -976,21 +1001,21 @@ export class Router<
S extends State = RS,
>(
nameOrPath: string,
- pathOrMiddleware: string | RouterMiddleware,
- ...middleware: RouterMiddleware[]
+ pathOrMiddleware: string | RouterMiddlewareOrMiddlewareObject,
+ ...middleware: RouterMiddlewareOrMiddlewareObject[]
): Router;
head<
P extends RouteParams = RouteParams,
S extends State = RS,
>(
nameOrPath: string,
- pathOrMiddleware: string | RouterMiddleware,
- ...middleware: RouterMiddleware[]
+ pathOrMiddleware: string | RouterMiddlewareOrMiddlewareObject,
+ ...middleware: RouterMiddlewareOrMiddlewareObject[]
): Router {
this.#useVerb(
nameOrPath,
- pathOrMiddleware as (string | RouterMiddleware),
- middleware as RouterMiddleware[],
+ pathOrMiddleware as (string | RouterMiddlewareOrMiddlewareObject),
+ middleware as RouterMiddlewareOrMiddlewareObject[],
["HEAD"],
);
return this;
@@ -1013,8 +1038,8 @@ export class Router<
>(
name: string,
path: R,
- middleware: RouterMiddleware,
- ...middlewares: RouterMiddleware[]
+ middleware: RouterMiddlewareOrMiddlewareObject,
+ ...middlewares: RouterMiddlewareOrMiddlewareObject[]
): Router;
/** Register middleware for the specified routes when the `OPTIONS`,
* method is requested. */
@@ -1024,8 +1049,8 @@ export class Router<
S extends State = RS,
>(
path: R,
- middleware: RouterMiddleware,
- ...middlewares: RouterMiddleware[]
+ middleware: RouterMiddlewareOrMiddlewareObject,
+ ...middlewares: RouterMiddlewareOrMiddlewareObject[]
): Router;
/** Register middleware for the specified routes when the `OPTIONS`,
* method is requested with explicit path parameters. */
@@ -1034,21 +1059,21 @@ export class Router<
S extends State = RS,
>(
nameOrPath: string,
- pathOrMiddleware: string | RouterMiddleware,
- ...middleware: RouterMiddleware[]
+ pathOrMiddleware: string | RouterMiddlewareOrMiddlewareObject,
+ ...middleware: RouterMiddlewareOrMiddlewareObject[]
): Router;
options<
P extends RouteParams = RouteParams,
S extends State = RS,
>(
nameOrPath: string,
- pathOrMiddleware: string | RouterMiddleware,
- ...middleware: RouterMiddleware[]
+ pathOrMiddleware: string | RouterMiddlewareOrMiddlewareObject,
+ ...middleware: RouterMiddlewareOrMiddlewareObject[]
): Router {
this.#useVerb(
nameOrPath,
- pathOrMiddleware as (string | RouterMiddleware),
- middleware as RouterMiddleware[],
+ pathOrMiddleware as (string | RouterMiddlewareOrMiddlewareObject),
+ middleware as RouterMiddlewareOrMiddlewareObject[],
["OPTIONS"],
);
return this;
@@ -1076,8 +1101,8 @@ export class Router<
>(
name: string,
path: R,
- middleware: RouterMiddleware,
- ...middlewares: RouterMiddleware[]
+ middleware: RouterMiddlewareOrMiddlewareObject,
+ ...middlewares: RouterMiddlewareOrMiddlewareObject[]
): Router;
/** Register middleware for the specified routes when the `PATCH`,
* method is requested. */
@@ -1087,8 +1112,8 @@ export class Router<
S extends State = RS,
>(
path: R,
- middleware: RouterMiddleware,
- ...middlewares: RouterMiddleware[]
+ middleware: RouterMiddlewareOrMiddlewareObject,
+ ...middlewares: RouterMiddlewareOrMiddlewareObject[]
): Router;
/** Register middleware for the specified routes when the `PATCH`,
* method is requested with explicit path parameters. */
@@ -1097,21 +1122,21 @@ export class Router<
S extends State = RS,
>(
nameOrPath: string,
- pathOrMiddleware: string | RouterMiddleware,
- ...middleware: RouterMiddleware[]
+ pathOrMiddleware: string | RouterMiddlewareOrMiddlewareObject,
+ ...middleware: RouterMiddlewareOrMiddlewareObject[]
): Router;
patch<
P extends RouteParams = RouteParams,
S extends State = RS,
>(
nameOrPath: string,
- pathOrMiddleware: string | RouterMiddleware,
- ...middleware: RouterMiddleware[]
+ pathOrMiddleware: string | RouterMiddlewareOrMiddlewareObject,
+ ...middleware: RouterMiddlewareOrMiddlewareObject[]
): Router {
this.#useVerb(
nameOrPath,
- pathOrMiddleware as (string | RouterMiddleware),
- middleware as RouterMiddleware[],
+ pathOrMiddleware as (string | RouterMiddlewareOrMiddlewareObject),
+ middleware as RouterMiddlewareOrMiddlewareObject[],
["PATCH"],
);
return this;
@@ -1126,8 +1151,8 @@ export class Router<
>(
name: string,
path: R,
- middleware: RouterMiddleware,
- ...middlewares: RouterMiddleware[]
+ middleware: RouterMiddlewareOrMiddlewareObject,
+ ...middlewares: RouterMiddlewareOrMiddlewareObject[]
): Router;
/** Register middleware for the specified routes when the `POST`,
* method is requested. */
@@ -1137,8 +1162,8 @@ export class Router<
S extends State = RS,
>(
path: R,
- middleware: RouterMiddleware,
- ...middlewares: RouterMiddleware[]
+ middleware: RouterMiddlewareOrMiddlewareObject,
+ ...middlewares: RouterMiddlewareOrMiddlewareObject[]
): Router;
/** Register middleware for the specified routes when the `POST`,
* method is requested with explicit path parameters. */
@@ -1147,21 +1172,21 @@ export class Router<
S extends State = RS,
>(
nameOrPath: string,
- pathOrMiddleware: string | RouterMiddleware,
- ...middleware: RouterMiddleware[]
+ pathOrMiddleware: string | RouterMiddlewareOrMiddlewareObject,
+ ...middleware: RouterMiddlewareOrMiddlewareObject[]
): Router;
post<
P extends RouteParams = RouteParams,
S extends State = RS,
>(
nameOrPath: string,
- pathOrMiddleware: string | RouterMiddleware,
- ...middleware: RouterMiddleware[]
+ pathOrMiddleware: string | RouterMiddlewareOrMiddlewareObject,
+ ...middleware: RouterMiddlewareOrMiddlewareObject[]
): Router {
this.#useVerb(
nameOrPath,
- pathOrMiddleware as (string | RouterMiddleware),
- middleware as RouterMiddleware[],
+ pathOrMiddleware as (string | RouterMiddlewareOrMiddlewareObject),
+ middleware as RouterMiddlewareOrMiddlewareObject[],
["POST"],
);
return this;
@@ -1186,8 +1211,8 @@ export class Router<
>(
name: string,
path: R,
- middleware: RouterMiddleware,
- ...middlewares: RouterMiddleware[]
+ middleware: RouterMiddlewareOrMiddlewareObject,
+ ...middlewares: RouterMiddlewareOrMiddlewareObject[]
): Router