npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@loupeat/fmiddleware

v1.1.0

Published

Framework-agnostic HTTP middleware for Express and AWS Lambda

Readme

@loupeat/fmiddleware

A framework-agnostic HTTP middleware for building APIs that run on both Express.js and AWS Lambda.

Features

  • Framework-agnostic: Write your API once, deploy to Express.js or AWS Lambda
  • Powerful routing: Path parameters, wildcards, and pattern matching
  • Pre-processors: Enrich requests with authentication, context, and validation
  • Post-processors: Handle errors, logging, and response transformation
  • Built-in validation: JSON Schema validation with custom keywords (uuid, email, json)
  • Typed errors: Semantic error classes that map to HTTP status codes
  • TypeScript-first: Full type safety for requests and responses
  • OpenAPI generation: Generate OpenAPI 3.0 specs automatically from handlers

Installation

npm install @loupeat/fmiddleware

For AWS Lambda support, also install the types:

npm install --save-dev @types/aws-lambda

Quick Start

Express.js

import express from "express";
import { FExpressMiddleware, FRequest } from "@loupeat/fmiddleware";

const app = express();
const api = new FExpressMiddleware();

interface Note {
  id: string;
  title: string;
}

// Register a simple GET endpoint
api.get("/api/notes", async (request: FRequest<any, any>) => {
  const notes: Note[] = [{ id: "1", title: "Hello World" }];
  return api.responses.OK<any, Note[]>(request, notes);
});

// Use FMiddleware as Express middleware
app.use(express.json());
app.all("*", async (req, res) => {
  const response = await api.process(req);
  for (const [key, value] of Object.entries(response.headers || {})) {
    res.setHeader(key, value as string);
  }
  res.status(response.statusCode).json(response.body);
});

app.listen(3000);

AWS Lambda

import { APIGatewayProxyHandler } from "aws-lambda";
import { FAWSLambdaMiddleware, FRequest } from "@loupeat/fmiddleware";

interface Note {
  id: string;
  title: string;
}

const api = new FAWSLambdaMiddleware();

api.get("/api/notes", async (request: FRequest<any, any>) => {
  const notes: Note[] = [{ id: "1", title: "Hello World" }];
  return api.responses.OK<any, Note[]>(request, notes);
});

export const handler: APIGatewayProxyHandler = async (event) => {
  return api.process(event);
};

Routing

Registering Handlers

FMiddleware provides methods for all common HTTP verbs:

api.get(path, handler);
api.post(path, handler, schema?);
api.put(path, handler, schema?);
api.delete(path, handler);

Path Parameters

Capture dynamic segments using {paramName} syntax:

import { FRequest } from "@loupeat/fmiddleware";

api.get("/api/notes/{noteId}", async (request: FRequest<any, any>) => {
  const noteId = api.pathParameter(request, "noteId");
  const note = await notesService.get(noteId);

  if (!note) {
    return api.responses.NotFound(request, `Note ${noteId} not found`);
  }

  return api.responses.OK<any, Note>(request, note);
});

Greedy Path Parameters

Capture multiple path segments using {paramName+}:

interface FilePathResponse {
  filepath: string;
}

api.get("/api/files/{filepath+}", async (request: FRequest<any, any>) => {
  // For /api/files/documents/2024/report.pdf
  // filepath = "documents/2024/report.pdf"
  const filepath = api.pathParameter(request, "filepath");
  return api.responses.OK<any, FilePathResponse>(request, { filepath });
});

Security Warning: Path parameters can contain traversal sequences like ../. If you use path parameters for file system operations, always validate that the resolved path stays within your intended directory:

import * as path from "path";

const baseDir = "/var/uploads";
const userPath = api.pathParameter(request, "filepath");
const resolved = path.resolve(baseDir, userPath);

if (!resolved.startsWith(baseDir)) {
  throw new ForbiddenError("Invalid file path");
}

Query Parameters

api.get("/api/notes/search", async (request: FRequest<any, any>) => {
  // Required parameter - throws ValidationError if missing
  const query = api.queryStringParameter(request, "q");

  // Optional parameter - returns undefined if missing
  const tag = api.queryStringParameterOptional(request, "tag");

  const results = await notesService.search(query, tag);
  return api.responses.OK<any, Note[]>(request, results);
});

Path Patterns

FMiddleware supports wildcards for pre/post-processors:

| Pattern | Matches | |---------|---------| | /api/notes | Exact match | | /api/notes/* | Single segment wildcard | | /api/notes/** | Multi-segment wildcard | | /api/notes/{id} | Path parameter | | /api/notes/{path+} | Greedy path parameter |

Request Validation

Validate request bodies using JSON Schema (Draft-07):

import { FRequest } from "@loupeat/fmiddleware";

interface CreateNoteRequest {
  title: string;
  content: string;
  tags?: string[];
}

const CreateNoteSchema = {
  type: "object",
  properties: {
    title: { type: "string", minLength: 1 },
    content: { type: "string" },
    tags: {
      type: "array",
      items: { type: "string" }
    }
  },
  required: ["title", "content"]
};

api.post("/api/notes", async (request: FRequest<any, CreateNoteRequest>) => {
  // request.body is validated against the schema and typed
  const { title, content, tags } = request.body;
  const note = await notesService.create({ title, content, tags });
  return api.responses.OK<CreateNoteRequest, Note>(request, note);
}, CreateNoteSchema);

Custom Validation Keywords

The built-in validator supports custom keywords:

// UUID validation
const schema = {
  type: "object",
  properties: {
    id: { type: "string", uuid: true }
  }
};

// Email validation
const schema = {
  type: "object",
  properties: {
    email: { type: "string", email: true }
  }
};

// JSON string validation
const schema = {
  type: "object",
  properties: {
    metadata: { type: "string", json: true }
  }
};

Pre-Processors

Pre-processors run before the handler and can enrich the request context:

import {
  FMiddleware,
  FRequest,
  FHandler,
  RequestPreProcessor,
  AuthenticationError
} from "@loupeat/fmiddleware";

interface User {
  id: string;
  email: string;
}

const AuthPreProcessor: RequestPreProcessor = {
  name: "AuthPreProcessor",
  pathPatterns: ["/api/notes/**"],
  requestSource: "*", // "express", "aws-lambda", or "*" for both
  process: async (
    api: FMiddleware<any, any>,
    request: FRequest<any, any>,
    handler: FHandler<any, any>
  ) => {
    const authHeader = request.headers["authorization"];

    if (!authHeader) {
      throw new AuthenticationError("Missing authorization header");
    }

    const token = authHeader.replace(/^Bearer /, "");
    const user = await authService.verifyToken(token);

    // Add user to request context
    request.context["user"] = user;
  }
};

// Register the pre-processor
api.addRequestPreProcessor(AuthPreProcessor);

// Access context in handlers
api.get("/api/notes", async (request: FRequest<any, any>) => {
  const user = api.context<User>(request, "user");
  const notes = await notesService.listByUser(user.id);
  return api.responses.OK<any, Note[]>(request, notes);
});

Pre-Processor Options

const MyPreProcessor: RequestPreProcessor = {
  name: "MyPreProcessor",
  pathPatterns: ["/api/**"],      // Which paths to match
  httpMethods: [FHttpMethod.POST, FHttpMethod.PUT], // Optional: specific methods only
  requestSource: "*",              // "express", "aws-lambda", or "*"
  process: async (api, request, handler) => {
    // Your logic here
  }
};

Post-Processors

Post-processors run after the handler and can transform responses or handle errors:

import { FMiddleware, FResponse, ResponsePostProcessor } from "@loupeat/fmiddleware";

const LoggingPostProcessor: ResponsePostProcessor = {
  name: "LoggingPostProcessor",
  pathPatterns: ["/**"],
  requestSource: "*",
  process: async (api: FMiddleware<any, any>, response: FResponse<any, any, any>) => {
    console.log(`${response.request.httpMethod} ${response.request.path} - ${response.statusCode}`);

    if (response.error) {
      console.error("Request failed:", response.error);
    }
  }
};

api.addResponsePostProcessor(LoggingPostProcessor);

Error Handling

FMiddleware provides semantic error classes that automatically map to HTTP status codes:

import {
  FRequest,
  ValidationError,     // 400 Bad Request
  AuthenticationError, // 401 Unauthorized
  ForbiddenError,      // 403 Forbidden
  NotFoundError,       // 404 Not Found
  ConflictError        // 409 Conflict
} from "@loupeat/fmiddleware";

interface User {
  id: string;
}

api.get("/api/notes/{noteId}", async (request: FRequest<any, any>) => {
  const noteId = api.pathParameter(request, "noteId");
  const user = api.context<User>(request, "user");

  const note = await notesService.get(noteId);

  if (!note) {
    throw new NotFoundError(`Note ${noteId} not found`);
  }

  if (note.userId !== user.id) {
    throw new ForbiddenError("You don't have access to this note");
  }

  return api.responses.OK<any, Note>(request, note);
});

Response Helpers

interface Note {
  id: string;
  title: string;
}

// 200 OK with typed body
api.responses.OK<any, Note>(request, { id: "1", title: "Hello" });

// 200 OK with custom headers
api.responses.OK<any, Note>(request, note, { "Cache-Control": "max-age=60" });

// 204 No Content
api.responses.NoContent(request);

// 400 Bad Request
api.responses.BadRequest(request, "Invalid input");

// 404 Not Found
api.responses.NotFound(request, "Resource not found");

// Custom status code with typed body
api.responses._<any, Note>(request, 201, { id: "1", title: "Created" });

Complete Example: Notes API

Here's a complete example of a Notes API with authentication:

import {
  FExpressMiddleware,
  FMiddleware,
  FRequest,
  FHandler,
  RequestPreProcessor,
  AuthenticationError,
  NotFoundError,
  ForbiddenError,
  validator
} from "@loupeat/fmiddleware";

// Types
interface User {
  id: string;
  email: string;
}

interface Note {
  id: string;
  userId: string;
  title: string;
  content: string;
  tags: string[];
}

interface CreateNoteRequest {
  title: string;
  content: string;
  tags?: string[];
}

interface UpdateNoteRequest {
  title?: string;
  content?: string;
  tags?: string[];
}

// Schemas
const CreateNoteSchema = {
  type: "object",
  properties: {
    title: { type: "string", minLength: 1 },
    content: { type: "string" },
    tags: { type: "array", items: { type: "string" } }
  },
  required: ["title", "content"]
};

const UpdateNoteSchema = {
  type: "object",
  properties: {
    title: { type: "string", minLength: 1 },
    content: { type: "string" },
    tags: { type: "array", items: { type: "string" } }
  }
};

// Initialize middleware
const api = new FExpressMiddleware();

// Authentication pre-processor
const AuthPreProcessor: RequestPreProcessor = {
  name: "AuthPreProcessor",
  pathPatterns: ["/api/notes/**", "/api/notes"],
  requestSource: "*",
  process: async (
    _api: FMiddleware<any, any>,
    request: FRequest<any, any>,
    _handler: FHandler<any, any>
  ) => {
    const authHeader = request.headers["authorization"];
    if (!authHeader) {
      throw new AuthenticationError("Missing authorization header");
    }

    const token = authHeader.replace(/^Bearer /, "");
    const user = await verifyToken(token); // Your auth logic
    request.context["user"] = user;
  }
};

api.addRequestPreProcessor(AuthPreProcessor);

// Routes
export function registerNotesApi(api: FExpressMiddleware) {

  // List all notes for user
  api.get("/api/notes", async (request: FRequest<any, any>) => {
    const user = api.context<User>(request, "user");
    const notes = await notesService.listByUser(user.id);
    return api.responses.OK<any, Note[]>(request, notes);
  });

  // Create a new note
  api.post("/api/notes", async (request: FRequest<any, CreateNoteRequest>) => {
    const user = api.context<User>(request, "user");
    const { title, content, tags } = request.body;

    const note = await notesService.create({
      userId: user.id,
      title,
      content,
      tags: tags || []
    });

    return api.responses.OK<CreateNoteRequest, Note>(request, note);
  }, CreateNoteSchema);

  // Get a specific note
  api.get("/api/notes/{noteId}", async (request: FRequest<any, any>) => {
    const user = api.context<User>(request, "user");
    const noteId = api.pathParameter(request, "noteId");
    validator.validateUuid(noteId);

    const note = await notesService.get(noteId);

    if (!note) {
      throw new NotFoundError(`Note ${noteId} not found`);
    }

    if (note.userId !== user.id) {
      throw new ForbiddenError("Access denied");
    }

    return api.responses.OK<any, Note>(request, note);
  });

  // Update a note
  api.put("/api/notes/{noteId}", async (request: FRequest<any, UpdateNoteRequest>) => {
    const user = api.context<User>(request, "user");
    const noteId = api.pathParameter(request, "noteId");
    validator.validateUuid(noteId);

    const note = await notesService.get(noteId);

    if (!note) {
      throw new NotFoundError(`Note ${noteId} not found`);
    }

    if (note.userId !== user.id) {
      throw new ForbiddenError("Access denied");
    }

    const updated = await notesService.update(noteId, request.body);
    return api.responses.OK<UpdateNoteRequest, Note>(request, updated);
  }, UpdateNoteSchema);

  // Delete a note
  api.delete("/api/notes/{noteId}", async (request: FRequest<any, any>) => {
    const user = api.context<User>(request, "user");
    const noteId = api.pathParameter(request, "noteId");
    validator.validateUuid(noteId);

    const note = await notesService.get(noteId);

    if (!note) {
      throw new NotFoundError(`Note ${noteId} not found`);
    }

    if (note.userId !== user.id) {
      throw new ForbiddenError("Access denied");
    }

    await notesService.delete(noteId);
    return api.responses.NoContent(request);
  });

  // Search notes
  api.get("/api/notes/search", async (request: FRequest<any, any>) => {
    const user = api.context<User>(request, "user");
    const query = api.queryStringParameterOptional(request, "q") || "";
    const tag = api.queryStringParameterOptional(request, "tag");

    const notes = await notesService.search(user.id, { query, tag });
    return api.responses.OK<any, Note[]>(request, notes);
  });
}

AWS Lambda Deployment

FMiddleware works great with serverless frameworks. Here's how to deploy to AWS Lambda.

With Serverless Framework

We recommend using Serverless Framework or AWS CDK for Lambda deployments.

serverless.yml:

service: notes-api

plugins:
  - serverless-esbuild  # For TypeScript bundling

custom:
  esbuild:
    bundle: true
    minify: false
    sourcemap: true
    target: node20

provider:
  name: aws
  runtime: nodejs20.x
  region: eu-west-1
  environment:
    LOG_LEVEL: info

functions:
  api:
    handler: src/handler.main
    events:
      - http:
          method: any
          path: "api/{proxy+}"
          cors: true
    timeout: 15

src/handler.ts:

import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";
import { FAWSLambdaMiddleware } from "@loupeat/fmiddleware";
import { registerNotesApi } from "./notes-api";

let api: FAWSLambdaMiddleware;

export const main = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
  // Initialize once per cold start
  if (!api) {
    api = new FAWSLambdaMiddleware();
    registerNotesApi(api);
  }

  return api.process(event);
};

Splitting by Authentication Context

A common pattern is to split Lambda functions by authentication context rather than by resource. This approach:

  • Optimizes cold starts: Public handlers don't load auth processors
  • Improves security: Authentication code is isolated to protected functions
  • Enables different configurations: More memory/timeout for authenticated requests

serverless.yml:

functions:
  # Public endpoints - no authentication
  public:
    handler: src/lambda.publicHandler
    events:
      - http:
          method: any
          path: "api/public/{proxy+}"
          cors: true
    timeout: 15
    memorySize: 256

  # Private endpoints - requires JWT
  private:
    handler: src/lambda.privateHandler
    events:
      - http:
          method: any
          path: "api/private/{proxy+}"
          cors: true
          authorizer:
            type: COGNITO_USER_POOLS
            authorizerId:
              Ref: ApiGatewayAuthorizer
    timeout: 30
    memorySize: 512

src/lambda.ts:

import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";
import { FAWSLambdaMiddleware } from "@loupeat/fmiddleware";
import { registerApi } from "./api";

let publicApi: FAWSLambdaMiddleware;
let privateApi: FAWSLambdaMiddleware;

// Public handler - no auth required
export const publicHandler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
  if (!publicApi) {
    publicApi = new FAWSLambdaMiddleware();
    publicApi.setPathPrefix("/api/public");
    registerApi(publicApi);
  }
  return publicApi.process(event);
};

// Private handler - JWT validated by API Gateway
export const privateHandler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
  if (!privateApi) {
    privateApi = new FAWSLambdaMiddleware();
    privateApi.setPathPrefix("/api/private");
    registerApi(privateApi);
  }
  return privateApi.process(event);
};

The setPathPrefix() method ensures each Lambda only registers handlers matching its prefix, reducing initialization time and memory usage.

Express.js Integration

src/app.ts:

import express, { Request, Response } from "express";
import { FExpressMiddleware, FResponse } from "@loupeat/fmiddleware";
import { registerNotesApi } from "./notes-api";

const app = express();
const api = new FExpressMiddleware();

// Register your routes
registerNotesApi(api);

// Parse JSON bodies
app.use(express.json());

// Route all requests through FMiddleware
app.all("*", async (req: Request, res: Response) => {
  const response: FResponse<any, any, any> = await api.process(req);

  // Set headers
  for (const [key, value] of Object.entries(response.headers || {})) {
    res.setHeader(key, value as string);
  }

  // Send response
  res.status(response.statusCode).json(response.body);
});

app.listen(3000, () => {
  console.log("Server running on http://localhost:3000");
});

Security Note: request.sourceIp in Express comes from request.ip, which respects the X-Forwarded-For header if trust proxy is enabled. If your Express app runs behind a reverse proxy (nginx, load balancer), configure trust proxy correctly. If running without a proxy, ensure trust proxy is disabled to prevent IP spoofing via the X-Forwarded-For header. See Express trust proxy documentation.

Configuration

Logging

Set the log level via environment variable:

LOG_LEVEL=debug  # debug, info, warn, error

Default Headers

Both FExpressMiddleware and FAWSLambdaMiddleware set a CORS header by default:

{
  "Access-Control-Allow-Origin": "*"
}

You can add custom headers to responses:

api.responses.OK<any, Note>(request, note, api.headers({
  "Cache-Control": "max-age=60",
  "X-Custom-Header": "value"
}));

OpenAPI Generation

FMiddleware includes built-in support for generating OpenAPI 3.0 specifications from your registered handlers.

Adding OpenAPI Metadata to Handlers

You can enrich handlers with OpenAPI metadata for better documentation:

import { FExpressMiddleware, OpenAPIMetadata } from "@loupeat/fmiddleware";

const api = new FExpressMiddleware();

// GET with OpenAPI metadata
api.get("/api/notes", async (request) => {
  const notes = await notesService.list();
  return api.responses.OK(request, notes);
}, {
  summary: "List all notes",
  description: "Retrieves all notes for the authenticated user",
  tags: ["Notes"],
  queryParams: [
    { name: "tag", description: "Filter by tag", required: false },
    { name: "limit", description: "Max results", schema: { type: "integer" } }
  ],
  responseSchema: {
    type: "array",
    items: { $ref: "#/components/schemas/Note" }
  }
});

// POST with schema and OpenAPI metadata
api.post("/api/notes", async (request) => {
  const note = await notesService.create(request.body);
  return api.responses.OK(request, note);
}, CreateNoteSchema, {
  summary: "Create a note",
  tags: ["Notes"],
  requestBodyDescription: "Note to create"
});

OpenAPI Metadata Options

interface OpenAPIMetadata {
  summary?: string;           // Brief description
  description?: string;       // Detailed description
  tags?: string[];            // Categorization tags
  operationId?: string;       // Unique operation ID
  deprecated?: boolean;       // Mark as deprecated
  queryParams?: QueryParamDef[];  // Query parameter definitions
  pathParams?: Record<string, PathParamDef>;  // Path parameter descriptions
  responseSchema?: any;       // JSON Schema for response
  responses?: Record<string, ResponseDef>;    // Custom response definitions
  requestBodyDescription?: string;  // Description for request body
}

Generating OpenAPI Specifications

Use the OpenApiGenerator class to generate specs from your middleware:

import {
  FExpressMiddleware,
  OpenApiGenerator,
  GeneratorConfig
} from "@loupeat/fmiddleware";
import * as fs from "fs";

// Initialize and register handlers
const api = new FExpressMiddleware();
registerAllRoutes(api);

// Configure the generator
const config: GeneratorConfig = {
  info: {
    title: "My API",
    version: "1.0.0",
    description: "API description"
  },
  servers: [
    { url: "https://api.example.com", description: "Production" }
  ],
  tags: [
    { name: "Notes", description: "Note management" }
  ],
  securitySchemes: {
    bearerAuth: {
      type: "http",
      scheme: "bearer",
      bearerFormat: "JWT"
    }
  },
  // Custom security inference based on path
  securityInference: (path) => {
    if (path.includes("/public/")) return [];
    return [{ bearerAuth: [] }];
  },
  // Custom tag inference based on path
  tagInference: (path) => {
    const match = path.match(/\/api\/(\w+)/);
    return match ? [match[1]] : ["General"];
  }
};

// Generate the spec
const generator = new OpenApiGenerator(api, config);
const spec = generator.generate();

// Write to file
fs.writeFileSync("openapi.json", JSON.stringify(spec, null, 2));

Generator Config Options

interface GeneratorConfig {
  info: {
    title: string;
    version: string;
    description?: string;
  };
  servers?: Array<{ url: string; description?: string }>;
  tags?: Array<{ name: string; description?: string }>;
  securitySchemes?: Record<string, any>;
  securityInference?: (path: string) => any[];  // Custom security logic
  tagInference?: (path: string) => string[];    // Custom tag logic
}

Automatic Inference

The generator automatically infers:

  • Path parameters from {param} patterns in routes
  • Operation IDs from HTTP method + path
  • Summaries from HTTP method + resource name
  • Request body schemas from handler schema parameter
  • Standard error responses (400, 401, 403, 404, 500)

API Reference

Core Types

// Request type with generics for original request and body type
FRequest<OriginalRequestType, RequestBodyType>

// Response type with generics
FResponse<OriginalRequestType, RequestBodyType, ResponseBodyType>

// Handler function signature
(request: FRequest<any, RequestBodyType>) => Promise<FResponse<any, RequestBodyType, ResponseBodyType>>

FMiddleware

| Method | Description | |--------|-------------| | get(path, handler, openapi?) | Register GET handler with optional OpenAPI metadata | | post(path, handler, schema?, openapi?) | Register POST handler with validation and OpenAPI metadata | | put(path, handler, schema?, openapi?) | Register PUT handler with validation and OpenAPI metadata | | delete(path, handler, schema?, openapi?) | Register DELETE handler with optional OpenAPI metadata | | addRequestPreProcessor(processor) | Add a pre-processor | | addResponsePostProcessor(processor) | Add a post-processor | | pathParameter(request, name) | Get path parameter value | | queryStringParameter(request, name) | Get required query parameter | | queryStringParameterOptional(request, name) | Get optional query parameter | | context<T>(request, key) | Get typed value from request context | | setPathPrefix(prefix) | Only register handlers matching prefix | | getHandlers() | Get all registered handlers (for OpenAPI generation) | | responses.OK<Req, Res>(request, body) | Return 200 with typed response | | responses.NoContent(request) | Return 204 | | responses.NotFound(request, message) | Return 404 | | responses.BadRequest(request, message) | Return 400 | | responses._(request, status, body) | Return custom status code |

Error Classes

| Class | HTTP Status | |-------|-------------| | ValidationError | 400 | | AuthenticationError | 401 | | ForbiddenError | 403 | | NotFoundError | 404 | | ConflictError | 409 |

OpenAPI Types

| Type | Description | |------|-------------| | OpenAPIMetadata | Metadata to attach to handlers for documentation | | QueryParamDef | Query parameter definition | | PathParamDef | Path parameter description | | ResponseDef | Response definition | | OpenAPISpec | Full OpenAPI 3.0 specification | | GeneratorConfig | Configuration for OpenApiGenerator | | OpenApiGenerator | Class to generate OpenAPI specs from middleware |

License

MIT