Skip to main content
Home

Built and signed on GitHub Actions

Functions to wrap HTTP handlers and intercept Requests and/or Responses. CORS, Logging, etc. Like middleware, but more outerwear.

This package works with Cloudflare Workers, Node.js, Deno, Bun, Browsers
This package works with Cloudflare Workers
This package works with Node.js
This package works with Deno
This package works with Bun
This package works with Browsers
JSR Score
100%
Published
4 weeks ago (0.27.0)

Request/Response Interceptors

Similar to middleware, but more outerwear.

What is Middleware?

Middleware in most HTTP routers is form of aspect-oriented programming, but it's generally limited to the around advice. A middleware function is given a next() function, that calls the next middleware or eventually the request handler itself, but it may perform any logic it wants around this call.

What are Interceptors?

Interceptors also take a leaf out of AOP, but instead let you define before and after advice.

Specifically:

  • before the request handler - allowing interception of the Request, and
  • after the handler - allowing interception of the Response

Interceptors are registered using the intercept() function that wraps the handler, which is given the set of interceptors, returning a new handler function with all the interception baked in.

Here's an example of a Request Interceptor that ensures an Authorization header is present...

Deno.serve(
  withFallback(
    intercept(
      // This is the main handler...
      () => new Response("Hello world"),
      {
        // This is a RequestInterceptor that requires the Request to have an
        // Authorization header otherwise responds with a `401 Unauthorized`,
        // and asks for credentials.
        request: (req) => {
          if (!req.headers.has("Authorization")) {
            return unauthorized(`Basic realm="Who are you?"`);
          }
        },
      },
    ),
  ),
);

Off the Shelf Interceptors

This package contains a number of pre-built interceptors...

  • catchResponse - an error interceptor that catches any thrown Responses and return it as the actual Response
  • cors - interceptors to handle CORS (Cross-Origin Resource Sharing) requests
  • logging - interceptors to log Request info and Response status to the console
  • skip - intercept Responses by status and convert to a 'skipped' (null) response, so that the request can then be delegated to another handler
  • verifyHeader - verify that a Request header matches an expected value

and also some utilities to wrap interceptors to change behaviour...

  • whenStatus - apply an interceptor only for specific response statuses
  • whenPattern - apply an interceptor only when the request URL matches a given pattern

These are in a similar vein to the by* functions of @http/route, the naming convention of when* is used to distinguish these interceptor functions from the routing functions.

Types of Interceptor

The intercept() function supports registration of five different types of interceptor:

These maybe supplied as the keys of an object, and you may supply a single function or and array of functions for each type. You may also supply either a single object or an array of these objects to the intercept function.

request, around and error interceptors are invoked in the order in which they are supplied, whereas response and finally are applied in reverse order. This ensures that a top-level feature that provides multiple types of interceptor (such as a logger) would be the first to intercept the Request, and the last to intercept the Response.

Request Interceptor

These are called before the Request is passed to the actual handler, and may be used to modify or block requests entirely before they get to the handler.

The Request Interceptor is a function, that accepts the incoming Request, and any additional arguments passed to the handler that is created by intercept.

It may return one of the following:

  • Nothing (undefined/void) - to indicate no action is to be taken, the original Request is free to pass to the next interceptor or to the handler (you may also just return the original Request)
  • A new or modified Request - to replace the incoming request, to pass to the next interceptor or the handler
  • A Response - to skip any further Request interceptors and the handler, this would pass to further Response interceptors though
  • null (if allowed) - indicate that this handler as a whole cannot handle the request, this is used in combination with cascade and withFallback functions of [@http/route] - but use of null and those functions is entirely optional.

The return value can optionally be wrapped in a Promise.

It may also throw an error, to be handled by the Error Interceptors.

So in our example above, we check the header and either return nothing, or an unauthorized Response.

Response Interceptor

These are called with the outgoing Response, after the handler, or an interceptor that returned a Response.

This is can be used, for example, to test whether something like a Not Modified response should be sent instead, or to render a full page for an error or a Not Found, or to add CORS or other custom headers.

Again, this just another function, that accepts the Request received by the handler (or interceptor), and the Response returned from it.

It may return one of the following:

  • Nothing (undefined/void) - to indicate no action is to be taken, and the original Response should be passed on
  • A new or modified Response - to replace the outgoing request, to pass to the next interceptor or eventually out from the server
  • null (if allowed) - indicate that this handler as a whole cannot handle the request, this is used in combination with cascade and withFallback functions - but use of null and those functions is entirely optional.

The return value can optionally be wrapped in a Promise.

It may also throw an error, to be handled by the Error Interceptors.

Around Interceptor

This is very similar to the traditional middleware, these are called immediately surrounding the handler, and so after the request interceptors and before the response interceptors.

Unlike traditional middleware though, this isn't a catch-all replacement for those other interceptors above. It's really to allow side-effects and wrapping of the handler.

The interceptor function accepts the Request and a next function which it MUST call and await.

A primary use-case for this is the requestContext interceptor, which stores the Request in an async context for later retrieval (via getRequest) without having to explicitly passed it through every function.

Use sparingly, request/response interceptors are generally more appropriate for most cases.

Error Interceptor

These are called if a Request or Response interceptor, or the handler throws an error. They can be used to provide a reasonable Response, and/or log the error.

An Error Interceptor function accepts the Request, optionally a Response (if one has been produced so far), and the error (or other object) that was caught.

It may return one of the following:

  • Nothing (undefined/void) - if it is unable to handle the error
  • A Response - to become the new outgoing Response, this will be passed to further Error Interceptors, and may still be passed to Response Interceptors if the throw occurred from a Request Interceptor or the handler.

The return value can optionally be wrapped in a Promise.

If an Error Interceptor throws an error, it will NOT be handled by any other interceptors and will immediately propagate out from the handler created by intercept().

Finally Interceptor

These are called when a Response has completed or been aborted, and after all data from it's body has been drained.

This is useful for logging, recording metrics, and cleaning up resources after a request.

The interceptor function accepts the Request, optionally a Response (if one was created before being aborted), and optionally the reason for the abort (from the dispatched abort event).

It should not return anything, but anything that is returned is ignored. These interceptors are not awaited.

It should not throw any errors, but any errors thrown are simply caught and logged to the console, and any further interceptors will continue to be called.

Shortcuts

interceptResponse()

Use of just Response Interceptors is the most common pattern, and so there is a shortcut to intercept() for just those.

This example looks for static files first using the staticRoute, which results in a 404 response if the file is not found. So we use the skip interceptor to convert any 404 response to a null (unhandled) response, the request can then be delegated to later handlers supplied to the handle function...

Deno.serve(
  handle([
    interceptResponse(
      staticRoute("/", import.meta.resolve("./public")),
      skip(404),
    ),
    byPattern("/hello", () => new Response("Hello")),
  ]),
);
Built and signed on
GitHub Actions

New Ticket: Report package

Please provide a reason for reporting this package. We will review your report and take appropriate action.

Please review the JSR usage policy before submitting a report.