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

aws-lambda-api-tools

v0.1.55

Published

A production-proven toolkit for building structured, type-safe REST APIs on AWS Lambda + API Gateway. Define routes declaratively, get automatic OpenAPI documentation, built-in validation, security, and — new in v0.1.39 — **async binding metadata** that t

Readme

AWS Lambda API Tools

A production-proven toolkit for building structured, type-safe REST APIs on AWS Lambda + API Gateway. Define routes declaratively, get automatic OpenAPI documentation, built-in validation, security, and — new in v0.1.39 — async binding metadata that tells AI agents and tooling which API calls have corresponding WebSocket completion events.

Why This Package

Building APIs on Lambda often means reinventing routing, validation, documentation, and security for every project. This package provides a batteries-included framework that:

  • Turns a single Lambda function into a full REST API with path-based routing
  • Generates OpenAPI/Swagger specs automatically from your Joi schemas
  • Handles JWT validation, CORS, security headers, and request validation as middleware
  • Runs locally with a built-in dev server (no SAM/Serverless Framework needed for iteration)
  • Declares async WebSocket bindings alongside REST routes for event-driven architectures
  • Sets up GitHub Actions OIDC IAM with a single command

Battle-tested in enterprise environments handling millions of requests across multiple production applications.

Features

  • Declarative Route Configuration — Define routes as data, not code. Method, path, handler, docs, auth, and async bindings in one object.
  • Automatic OpenAPI Generation — Joi request/response schemas convert to OpenAPI 3.0 specs. Run generate-oas and get a complete API spec.
  • Route-Module Codegen — Generate (and CI-verify) the static handler map from your route config with generate-route-modules. No more silent 404s from a forgotten registration.
  • Async Binding Metadata — Declare which WebSocket events correspond to each route's async results. Emitted as x-async-binding OpenAPI extensions for AI agents, SDK codegen, and documentation.
  • Middleware Chain — Composable, type-safe middleware. JWT validation, schema validation, response headers — stack them per-route.
  • Schema Validation — Joi-based request/response validation with automatic 400 error responses.
  • JWT Authentication — Built-in JWT validation middleware with token rotation support.
  • Security Configuration — CORS (exact, regex, pattern-based origins), security headers, credential handling — all from a JSON config file.
  • Response Header Helpers — Rate limiting, cache control, security headers, auth headers — add from any middleware.
  • Local Dev Server — createDevServer() runs your Lambda routes as a local HTTP server. No deploy needed during development.
  • GitHub Actions IAM — One command to create OIDC IAM roles for secure CI/CD deployments.
  • TypeScript First — Full type safety across route configs, handlers, middleware, and schemas.

Installation

npm install aws-lambda-api-tools

Quick Start

1. Define Route Configuration

// routes-config.ts
import { ConfigRouteEntry, RouteConfig } from 'aws-lambda-api-tools';

const routes: ConfigRouteEntry[] = [
  {
    description: 'Create a new user',
    swaggerMethodName: 'createUser',
    method: 'POST',
    path: '/api/v1/users',
    handlerPath: 'src/routes/users/create-user',
    generateOpenApiDocs: true,
    authorizeRoute: true,
  },
  {
    description: 'Get user by ID',
    swaggerMethodName: 'getUser',
    method: 'GET',
    path: '/api/v1/users/{userId}',
    handlerPath: 'src/routes/users/get-user',
    generateOpenApiDocs: true,
    authorizeRoute: true,
  },
  {
    description: 'Generate a report (async)',
    swaggerMethodName: 'generateReport',
    method: 'POST',
    path: '/api/v1/reports',
    handlerPath: 'src/routes/reports/generate-report',
    generateOpenApiDocs: true,
    authorizeRoute: true,
    asyncBinding: {
      event: 'report:completed',
      room: 'user:{userId}',
      description: 'Fires when the report generation completes',
      lifecycleEvents: [
        { event: 'report:progress', description: 'Progress percentage update' },
        { event: 'report:failed', description: 'Report generation failed' },
      ],
    },
  },
];

export const routeConfig: RouteConfig = {
  authorizeAllRoutes: false,
  routes,
  routesBaseUrlPath: '/api/v1',
};

2. Create Route Handlers

// src/routes/users/create-user.ts
import Joi from 'joi';
import {
  RouteModule,
  RouteSchema,
  RouteArguments,
  jwtValidationMiddleware,
  schemaValidationMiddleware,
} from 'aws-lambda-api-tools';

const routeSchema: RouteSchema = {
  requestBody: Joi.object({
    email: Joi.string().email().required(),
    name: Joi.string().min(1).max(100).required(),
  }),
  responseBody: Joi.object({
    id: Joi.string().required(),
    email: Joi.string().required(),
    name: Joi.string().required(),
    createdAt: Joi.string().isoDate().required(),
  }),
};

const handler = async (args: RouteArguments) => {
  const { email, name } = args.body;
  const user = await createUser({ email, name });
  return { statusCode: 201, body: user };
};

export default {
  routeChain: [jwtValidationMiddleware, schemaValidationMiddleware(routeSchema), handler],
  routeSchema,
} as RouteModule;

3. Register Route Modules

Because the Lambda is esbuild-bundled, handlers must be statically imported — a dynamic require(handlerPath) can't be bundled. So a route-modules.ts maps each route's handlerPath to its imported module:

// route-modules.ts
import { RouteModule } from 'aws-lambda-api-tools';
import createUser from './routes/users/create-user';
import getUser from './routes/users/get-user';
import generateReport from './routes/reports/generate-report';

export const routeModules: Record<string, RouteModule> = {
  'src/routes/users/create-user': createUser,
  'src/routes/users/get-user': getUser,
  'src/routes/reports/generate-report': generateReport,
};

Don't hand-maintain this. A route that's in the config but missing from this map is a silent 404 at runtime. Generate it from your route config instead:

npx aws-lambda-api-tools generate-route-modules

See Generate Route Modules below for wiring it into your build + CI.

4. Lambda Entry Point

// index.ts
import { lambdaRouteProxyEntryHandler } from 'aws-lambda-api-tools';
import { routeConfig } from './routes-config';
import { routeModules } from './route-modules';

export const handler = lambdaRouteProxyEntryHandler(routeConfig, routeModules);

That's it. One Lambda function serves your entire API with routing, validation, auth, and documentation.


Async Binding Metadata

Modern APIs often trigger asynchronous work — image generation, report compilation, video processing — where the HTTP response acknowledges the request, but the actual result arrives later via WebSocket or Server-Sent Events.

The asyncBinding field on ConfigRouteEntry declares this relationship at the source:

{
  description: 'Generate a video clip',
  swaggerMethodName: 'generateClip',
  method: 'POST',
  path: '/api/v1/clips/generate',
  handlerPath: 'src/routes/clips/generate-clip',
  generateOpenApiDocs: true,
  asyncBinding: {
    event: 'generation:completed',
    room: 'generation:{jobId}',
    description: 'Fires when the video clip generation pipeline completes',
    lifecycleEvents: [
      { event: 'generation:progress', description: 'Progress update with phase info' },
      { event: 'generation:queued', description: 'Job queued waiting for capacity' },
      { event: 'generation:failed', description: 'Generation failed with error details' },
    ],
  },
}

What This Produces

When you run generate-oas, the OpenAPI spec for this operation includes:

/api/v1/clips/generate:
  post:
    operationId: generateClip
    description: Generate a video clip
    x-async-binding:
      event: "generation:completed"
      room: "generation:{jobId}"
      description: "Fires when the video clip generation pipeline completes"
      lifecycleEvents:
        - event: "generation:progress"
          description: "Progress update with phase info"
        - event: "generation:queued"
          description: "Job queued waiting for capacity"
        - event: "generation:failed"
          description: "Generation failed with error details"

Who Consumes This

  • AI Agents — An agent calling generateClip() knows to subscribe to generation:{jobId} and wait for generation:completed before continuing its workflow.
  • API Client Codegen — Downstream tools can generate subscribe/unsubscribe helpers alongside REST methods.
  • Documentation — API docs can show developers which WebSocket events to listen for after calling an async endpoint.
  • Monitoring — Observability tools can correlate HTTP requests with their eventual async completions.

AsyncBindingConfig Reference

type AsyncBindingConfig = {
  /** WebSocket event name emitted on async completion */
  event: string;
  /** Room/channel pattern for subscription (e.g., 'generation:{jobId}', 'user:{userId}') */
  room: string;
  /** The response field carrying the job's id, when it is not named like the event's id field */
  idField?: string;
  /** Additional lifecycle events beyond completion */
  lifecycleEvents?: Array<{
    event: string;
    description?: string;
  }>;
  /** Description of when the completion event fires */
  description?: string;
  /** Joi schema for the completion event payload */
  payload?: Schema;
};

Route Configuration Reference

type ConfigRouteEntry = {
  /** Human-readable description (used in OpenAPI) */
  description: string;
  /** Operation ID / API client method name (e.g., 'createUser') */
  swaggerMethodName?: string;
  /** HTTP method */
  method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS' | 'ANY';
  /** Route path with {param} placeholders */
  path: string;
  /** Path to the handler module (must match key in routeModules) */
  handlerPath: string;
  /** Whether to include in generated OpenAPI docs */
  generateOpenApiDocs: boolean;
  /** Override auth for this route (true = require, false = skip) */
  authorizeRoute?: boolean;
  /** OpenAPI tag for grouping (auto-derived from path if not set) */
  tag?: string;
  /** Lambda function name override */
  functionName?: string;
  /** Async WebSocket binding metadata */
  asyncBinding?: AsyncBindingConfig;
  /** OpenAPI `x-` extensions for this operation, emitted exactly as given */
  extensions?: Record<`x-${string}`, unknown>;
};

type RouteConfig = {
  /** Apply auth to all routes by default */
  authorizeAllRoutes?: boolean;
  /** All route entries */
  routes: ConfigRouteEntry[];
  /** Base URL path for tag derivation (e.g., '/api/v1') */
  routesBaseUrlPath?: string;
  /** Security configuration */
  security?: SecurityConfig;
  /** Request logging: debug verbosity and extra redaction (secrets are always masked) */
  logging?: LoggingConfig;
  /** Resolve v2 events from rawPath instead of routeKey (required behind greedy routes) */
  useRawPath?: boolean;
  /** Document-level OpenAPI declarations: `securitySchemes` */
  openApi?: { securitySchemes?: Record<string, SecuritySchemeObject> };
  /** The body of every error response, as JSON (see Error Handling) */
  errorBody?: (failure: { statusCode: number; message: string; error: unknown }) => unknown;
};

Declared Responses

A route declares every response it can give, by status code, in routeSchema.responses: success and error alike. generate-oas emits each one on the operation, so a generated client types all of them.

const ErrorBody = Joi.object({
  error: Joi.object({ code: Joi.string().required(), message: Joi.string().required() }).required(),
}).meta({ className: 'ErrorBody' });

const routeSchema: RouteSchema = {
  params: { orderId: Joi.string().required() },
  requestBody: Joi.object({ quantity: Joi.number().integer().min(1).required() }).required(),
  responses: {
    '201': { description: 'The order was placed', body: Order },
    '204': { description: 'Nothing to place' },
    '400': { description: 'The request is invalid', body: ErrorBody },
    '404': { description: 'The account does not exist', body: ErrorBody },
  },
};
  • Keys are HTTP status codes (100–599) or default. Every response needs a description; one without a body has no content.
  • A named schema (.meta({ className })) becomes a component and is referenced by $ref, wherever it appears in a body.
  • A request body whose schema is .required() is emitted with required: true.
  • A route declares responses or responseBody, not both; generation fails naming the route. A route that declares neither keeps the single 200 it always had.

Standard OpenAPI Security

Each operation's security comes from what its middleware chain declares. The library holds no scheme names or permissions of its own: the product declares its schemes once in its route config, and marks its own middleware with declareSecurity.

import { declareSecurity } from 'aws-lambda-api-tools';

// Route config: the document's components.securitySchemes.
export const config: RouteConfig = {
  routes,
  openApi: {
    securitySchemes: {
      userToken: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },
      apiKey: { type: 'apiKey', in: 'header', name: 'x-api-key' },
    },
  },
};

// Authentication: any one of these schemes authenticates the request.
export const requireUserOrKey = declareSecurity(async (args) => { /* ... */ return args; }, {
  authenticates: ['userToken', 'apiKey'],
});

// Permissions: the authenticated caller must hold all of these.
export const requirePermission = (...permissions: string[]) =>
  declareSecurity(async (args) => { /* ... */ return args; }, { requires: permissions });

// Route module
export default { routeSchema, routeChain: [requireUserOrKey, requirePermission('orders:write')] };

generate-oas then emits, on that operation:

"security": [
  { "userToken": ["orders:write"] },
  { "apiKey": ["orders:write"] }
]
  • One entry per way of authenticating. Authentication steps in series must all pass, so their alternatives multiply.
  • Required permissions are listed as each scheme's scopes: OpenAPI 3.1's role names required for the execution.
  • A chain that declares no authentication gets no security: the document never guesses.
  • Generation fails, naming the route, when a chain names a scheme missing from openApi.securitySchemes, or requires permissions without declaring authentication.
  • declareSecurity returns the same middleware; its behaviour is unchanged.

OpenAPI Extensions

extensions on a route entry are emitted onto its operation exactly as given, so a product can declare its own x- data without the library knowing its meaning:

{
  swaggerMethodName: 'placeOrder',
  // ...
  extensions: { 'x-agent': { expose: true, effect: 'transaction' } },
}

Every key must start with x-. x-async-binding is emitted from asyncBinding, not from extensions.

buildOpenApiDocument(config, loadRouteModule, options) is the function behind generate-oas, for products that build their document in code.

OpenAPI 3.1

generate-oas --openapi 3.1 -- ./dist/routes-config.js writes schemas as OpenAPI 3.1 defines them (JSON Schema 2020-12). In code, pass buildOpenApiDocument(..., { openApiVersion: '3.1' }). Set your root document's openapi to 3.1.0 to match.

joi-to-swagger writes 3.0's dialect, and three of its keywords change:

  • nullable: true becomes a null type (["string", "null"], with null added to any enum). On a reference or composition, which has no type to widen, it becomes anyOf with { "type": "null" }. A 3.1 reader ignores nullable, so without this a field that admits null would read as one that does not.
  • Boolean exclusiveMinimum/exclusiveMaximum become numeric bounds.
  • example becomes examples.

toOpenApi31Schema and toOpenApi31Operation are exported for documents built elsewhere.


Greedy Proxy Routes

The router resolves every request itself, so API Gateway does not need one route per endpoint. Registering one per endpoint does not scale: an HTTP API allows 300 routes by default, and each route is a CloudFormation resource against a stack's 500-resource limit.

With useRawPath: true, register two ANY routes per URL group (the first segment after the base path) instead:

import { deriveUrlGroups, greedyPathsFor, findMisroutedRoutes } from 'aws-lambda-api-tools';

// CDK: two routes per group, e.g. ANY /api/v1/voices and ANY /api/v1/voices/{proxy+}
for (const group of deriveUrlGroups(routeConfig.routes, routeConfig.routesBaseUrlPath)) {
  for (const path of greedyPathsFor(group)) {
    new apigatewayv2.HttpRoute(this, `greedy-${path.replace(/[^a-zA-Z0-9]/g, '-')}`, {
      httpApi,
      integration,
      routeKey: apigatewayv2.HttpRouteKey.with(path, apigatewayv2.HttpMethod.ANY),
    });
  }
}

// Test: every declared route must still resolve to its own handler.
expect(findMisroutedRoutes(routeConfig.routes)).toEqual([]);
  • One derivation. Any code that registers or partitions routes must use deriveUrlGroups. Two derivations that differ make two stacks create the same route key, and the deploy fails with a 409.
  • No catch-all. urlGroupOf refuses a group that would answer every path: a parameter as the group segment (/api/v1/{id}), or a path outside the base path that would group as /api. deriveUrlGroups throws on those routes rather than leaving them unreachable.
  • Resolution moves into the Lambda. getRouteConfigByPath ranks static segments above parameters (/users/me beats /users/{userId}). findMisroutedRoutes checks the whole table: a route that resolves elsewhere would 404 or run the wrong handler. It also reports Express-style :param paths, which never resolve.
  • CORS preflight. ANY includes OPTIONS, so API Gateway forwards preflights instead of answering them from its CORS configuration. In rawPath mode the handler answers an OPTIONS request with no declared OPTIONS route with 204, before auth or module loading. API Gateway still adds its configured CORS headers.
  • Migration. CloudFormation creates the new routes before deleting the old ones, so the API briefly holds both sets. Keep that total under the routes-per-API quota.

Middleware

Built-in Middleware

JWT Validation

import { jwtValidationMiddleware } from 'aws-lambda-api-tools';

export default {
  routeChain: [jwtValidationMiddleware, handler],
  routeSchema,
} as RouteModule;

Validates the Authorization: Bearer <token> header. Decoded claims are available on args.routeData.

Schema Validation

import { schemaValidationMiddleware } from 'aws-lambda-api-tools';

const routeSchema: RouteSchema = {
  params: { userId: Joi.string().uuid().required() },
  query: { include: Joi.string().valid('posts', 'comments') },
  requestBody: Joi.object({ name: Joi.string().required() }),
  responseBody: Joi.object({ id: Joi.string(), name: Joi.string() }),
};

export default {
  routeChain: [schemaValidationMiddleware(routeSchema), handler],
  routeSchema,
} as RouteModule;

Response Header Helpers

Add headers from any middleware in the chain:

import {
  addResponseHeader,
  addRateLimitHeaders,
  addCacheHeaders,
  addSecurityHeaders,
} from 'aws-lambda-api-tools';

const rateLimitMiddleware = (args: RouteArguments) => {
  return addRateLimitHeaders(args, 100, 95, Date.now() + 3600000);
};

const cacheMiddleware = (args: RouteArguments) => {
  return addCacheHeaders(args, 300, { public: true, mustRevalidate: true });
};

Security Configuration

Create an api-security.json in your project root:

{
  "cors": {
    "allowOrigin": ["https://myapp.com", "https://staging.myapp.com"],
    "allowOriginPatterns": ["^https://[a-zA-Z0-9-]+\\.preview\\.myapp\\.com$"],
    "allowMethods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
    "allowHeaders": ["Content-Type", "Authorization"],
    "allowCredentials": true,
    "maxAge": 86400
  },
  "defaultHeaders": {
    "X-Content-Type-Options": "nosniff",
    "X-Frame-Options": "DENY",
    "X-XSS-Protection": "1; mode=block"
  },
  "jwtRotationHeaders": {
    "enabled": true,
    "rotationRequiredHeader": "X-Token-Rotation-Required",
    "rotationReasonHeader": "X-Token-Rotation-Reason"
  }
}

Supports exact string origins, regex patterns (as strings for JSON compatibility), and wildcard. Validates configuration on load and warns about insecure patterns.


Local Development Server

Run your Lambda routes locally without deploying:

import { createDevServer } from 'aws-lambda-api-tools';
import { routeConfig } from './routes-config';
import { routeModules } from './route-modules';

createDevServer({
  port: 3000,
  routeConfig,
  routeModules,
});
# Or as a script in package.json
node -r ts-node/register dev-server.ts

The dev server simulates API Gateway v2 events, handles CORS preflight, and provides the same routing behavior as production.


CLI Tools

Generate OpenAPI Spec

npx generate-oas

Reads your route configs and handler schemas, produces an OpenAPI 3.0 JSON spec including x-async-binding extensions.

Generate Route Modules

Generates the static route-modules.ts map from your *_routes-config files (the single source of truth), so the esbuild-bundled handler map can never drift from your route config. A missing entry would otherwise be a silent production 404.

# Write/refresh the map
npx aws-lambda-api-tools generate-route-modules

# Verify it's up to date (CI / pre-commit) — exits non-zero if stale,
# and reports routes declared-but-unregistered (404s) or dead entries.
npx aws-lambda-api-tools generate-route-modules --check

Options:

| Flag | Default | Description | |---|---|---| | --routes-dir <dir> | src/routes | Directory scanned recursively for *_routes-config* files | | --out <path> | src/route-modules.ts | Output file | | --type-import <module> | aws-lambda-api-tools | Module the RouteModule type is imported from | | --check | false | Verify only; exit non-zero if stale |

It scans config files for handlerPath declarations (no TypeScript execution required), so it's safe to run as a pre-build / pre-commit step. Recommended wiring:

// package.json
{
  "scripts": {
    "routes:gen": "aws-lambda-api-tools generate-route-modules",
    "prebuild": "npm run routes:gen",
    "predev": "npm run routes:gen"
  }
}

Add aws-lambda-api-tools generate-route-modules --check to CI / your pre-commit hook to fail fast if the map ever drifts.

A programmatic API is also exported: generateRouteModules, checkRouteModules, collectHandlerPaths, renderRouteModules, and handlerPathToIdentifier.

GitHub Actions IAM Setup

Create OIDC IAM roles for secure CI/CD (no long-lived credentials):

# Single repo
npx aws-lambda-api-tools create-gha-iam-stack --repo=myorg/my-service

# Multiple repos
npx aws-lambda-api-tools create-gha-iam-stack \
  --repo=myorg/service-a \
  --repo=myorg/service-b

# Custom policy
npx aws-lambda-api-tools create-gha-iam-stack \
  --repo=myorg/my-service \
  --policy=AWSLambda_FullAccess

# Manage repos
npx aws-lambda-api-tools create-gha-iam-stack --mode=list
npx aws-lambda-api-tools create-gha-iam-stack --remove-repo=myorg/old-service

Error Handling

import { CustomError } from 'aws-lambda-api-tools';

// Throws a structured error with HTTP status code
throw new CustomError('User not found', 404);
throw new CustomError('Insufficient permissions', 403);

By default an error's body is its message. Declare errorBody on the route config to give every error response one JSON body, the one your routes declare in responses:

class ApiError extends CustomError {
  constructor(message: string, statusCode: number, readonly code: string) {
    super(message, statusCode);
  }
}

export const config: RouteConfig = {
  routes,
  errorBody: ({ statusCode, message, error }) => ({
    error: {
      code: error instanceof ApiError ? error.code : `HTTP_${statusCode}`,
      // An unexpected error's message is internal; say so instead.
      message: error instanceof CustomError ? message : 'Internal server error',
    },
  }),
};

It applies to every error the library answers: an error thrown in a route's chain, a request body that is not JSON, and, in rawPath mode and the dev server, a path no route matches. error is what was thrown, or the library's own CustomError for a failure it detects.

A request body that is not valid JSON answers 400 (Request body is not valid JSON). It used to answer 500.


Agent-Ready APIs

When you declare asyncBinding on your routes, your API becomes agent-ready — AI agents can programmatically understand which endpoints trigger async work and how to subscribe for results.

This is the foundation for building AI assistants that can orchestrate complex workflows across your API:

  1. Agent calls POST /clips/generate → gets { jobId: "abc123" }
  2. Agent reads x-async-binding from the spec → knows to subscribe to room generation:abc123
  3. Agent listens for generation:progress, generation:completed, or generation:failed
  4. Agent continues its workflow when the async result arrives

No manual binding configuration needed. The API spec IS the contract.

Closure Agent SDK integration coming Summer 2026.


Roadmap

  • Zod schema support — Use Zod as an alternative to Joi for request/response validation and OpenAPI generation
  • AsyncAPI spec generation — Generate AsyncAPI documents from asyncBinding metadata alongside OpenAPI
  • WebSocket route support — First-class WebSocket route handlers with the same middleware pattern
  • Rate limiting middleware — Built-in token bucket / sliding window rate limiting
  • OpenTelemetry integration — Automatic tracing spans per route handler

Contributing

Contributions are welcome. Please open an issue to discuss proposed changes before submitting a PR.

License

MIT