Core Concepts

Middleware (Kernel)

A guide to the Middleware Kernel in Raptor

Introduction

What is the Kernel?

The Raptor Kernel component provides a lightweight mechanism for building applications using an elegant middleware stack.

What is Middleware?

Middleware sits between the request and response of an HTTP server. The middleware function receives an incoming request, allowing you to inspect it, transform it and respond immediately (or skip to the next middleware in the stack). This particular design gives you absolute flexibility - you can build anything from simple micro-services to large complex applications.

Usage

Configuration

An optional configuration object is available, allowing you to customise your kernel setup.

import type { Context } from "@raptor/types";
 
import {
  NotFound,
  type Config,
  DefaultServerManager,
  DefaultResponseManager,
  StringResponseProcessor
} from "@raptor/kernel";
 
export default {
  /**
   * The port the kernel will use to serve the application.
   *
   * @default 80
   */
  port: 8000,
 
  /**
   * The hostname the kernel will use to serve the appliation.
   *
   * @default "localhost"
   */
  hostname: "localhost";
 
  /**
   * Enable strict RFC 7231 content negotiation.
   *
   * When true, throws not acceptable if response cannot match accept header.
   * When false, returns content regardless of accept header.
   *
   * @default false
   */
  strictContentNegotiation: false;
 
  /**
   * Middleware stack, providing a way to add middleware to the kernel.
   *
   * @default []
   */
  middleware: [],
 
  /**
   * The custom error handler for catching and responding to system errors.
   *
   * @default undefined
   */
  errorHandler: (context: Context) => {
    const { error } = context;
 
    if (error instanceof NotFound) {
      return new Response("This page could not be found", {
        status: 404,
      });
    }
 
    return new Response(error.stack ?? error.message, {
      status: "status" in error ? error.status : 500,
    })
  },
 
  /**
   * Server configuration, providing a way to override the kernel's server management.
   */
  server: {
    /**
     * Custom server manager implementation for handling HTTP serving on the current runtime.
     *
     * Allows overriding the default server behavior.
     */
    manager: new DefaultServerManager();
  };
 
  /**
   * Response configuration, providing a way to override the kernel's response management.
   */
  response: {
    /**
     * Custom response manager implementation for processing response bodies into HTTP Response objects.
     *
     * Allows overriding the default response handling.
     */
    manager: new DefaultResponseManager(
      false,
    );
 
    /**
     * Custom processors for handling specific response body types (e.g., string, object, HTML).
     *
     * Key is the processor identifier, value is the processor implementation.
     */
    processors: {
      string: new StringResponseProcessor()
    };
  };
} satisfies Config;

For more information about the config-driven setup, see Configuration.

Adding Middleware

To add a new middleware to the stack, all you have to do is add a callback function using either the Kernel's use() method, add() method or by adding it to the Kernel's configuration options.

Using configuration:

import { Kernel } from "@raptor/kernel";
import type { Context } from "@raptor/types";
 
const app = new Kernel({
  middleware: [(context: Context) => "Hello, Dr Malcolm!"],
});
 
app.serve();

Using the Kernel methods:

import { Kernel } from "@raptor/kernel";
import type { Context, Next } from "@raptor/types";
 
const app = new Kernel();
 
app.use((_context: Context, _next: Next) => {
  return "Hello, Dr Malcolm!";
});
 
app.serve();

Calling the Next Middleware

The next() middleware function is responsible for invoking the subsequent middleware in the stack. It must be called if the current middleware doesn't handle the request and provide a response; otherwise, the system will fail to respond successfully.

As an example of calling the next middleware, the following script demonstrates how to calculate runtime across two middleware:

import type { Context, Next } from "@raptor/types";
 
app.use(async (_context: Context, next: Next) => {
  const start = Date.now();
 
  await next();
 
  const ms = Date.now() - start;
 
  console.log(`${ms}ms`);
});
 
app.use(async () => {
  await new Promise((resolve) => setTimeout(resolve, 3000));
 
  return "Hello, Dr Malcolm!";
});
 
// console: ~3004ms

Execution Order

Middleware are executed in the order they are added to the stack (Kernel), unless the next function has been called, otherwise that will alter the execution order.

Next Steps

For more information about Middleware, check out the Context documentation.

© 2026 Raptor