Middleware

Overview

npm i @webroute/middleware

Unlike traditional middleware paradigms, @webroute/middleware intentionally avoids framework-specific code, such as next() or req.user = user. Moreover, it promotes a more functional approach, using return values to infer information about how state changes, returning early Responses or registering Response handlers.

As a result, we can define middleware once, and use it with any web-standard framework. Learn more about this approach in Motivation.


MiddlewareFn

webroute middleware is the core interface that everything else is built on. Essentially anything that conforms to the following type signature is considered a webroute-compatible middleware. Additionally, it should not rely on any framework-specific code.

type DataResult = Record<PropertyKey, unknown>;
type ResponseHandler = (response: Response) => Response;
 
type MiddlewareFn = (
  request: Request
) => DataResult | ResponseHandler | undefined;
// (and/or as Promise)

In a nutshell, middleware can return:

  • nothing
  • data
  • a Response - to exit early
  • a Response handler, which is called on the egress journey

Any functions here can be async and can take any number of additional arguments. Unlike traditional middleware which is perfectly coupled with frameworks, this approach to middleware is more akin to calling a utility function, and then wiring up the various response types to the framework.

One could, therefore, provide a next() parameter to middleware. But this would defeat the purpose of this approach, and unduly couple the middleware to a given framework or assumption.

The MiddlewareFn type merely exists to enforces the above rules.

defineMiddleware

We can define middleware using the defineMiddleware function. Ultimately, this ensures our middleware is adhering to the MiddlewareFn signature, while still etaining all the type information.

const timeMiddleware = defineMiddleware((req, extraParam) => {
  const start = Date.now();
 
  return (res: Response) => {
    const diff = Date.now() - start;
 
    console.log("Request took", diff, "ms");
 
    return res;
  };
});

createAdapter

The createAdapter function allows us to declaratively define how webroute middleware can slot into an existing framework. Once an adapter has been defined, any webroute-comptaible middleware can be used with that framework, i.e. adapters only need to be defined once, not for each middleware implementation.

For example, a Hono adapter might look like this.

import { ,  } from "hono";
import { ,  } from "@webroute/middleware";
 
export const  = <>((, ) => {
  return {
    async () {
      .("state", { ....("state"), ... });
      ();
    },
    async () {
      ();
    },
    async () {
      return ;
    },
    async () {
      await ();
      return (.);
    },
  };
});
 
// Wire up `Request` and any other params
const  = (() =>
  (.., .("req-ip"))
);
 
const  = new ();
.("*", );

In the future, we may distribute these adapters as packages, unless snippets suffice.

On this page