Skip to content

Middleware

VentureKit provides a composable middleware system for cross-cutting concerns like logging, CORS, timeouts, and error handling.

Middleware wraps handler execution in a stack pattern. Each middleware can:

  • Run code before the handler
  • Run code after the handler
  • Catch and handle errors
  • Short-circuit the request (return a response without calling the handler)
Request → ErrorBoundary → Logging → Custom Middleware → Handler → Response

Catches all errors and formats them into structured JSON responses. This is applied automatically to every handler.

Logs request start, completion, and failures with timing information:

import { loggingMiddleware, createLogger } from '@venturekit/runtime';
const logger = createLogger({ minLevel: 'info' });
const logging = loggingMiddleware(logger);

Adds CORS headers and handles preflight OPTIONS requests:

import { corsMiddleware } from '@venturekit/runtime';
const cors = corsMiddleware({
allowOrigins: ['https://app.example.com'],
allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
allowHeaders: ['Content-Type', 'Authorization'],
allowCredentials: true,
maxAge: 3600,
});

Enforces a maximum request duration:

import { timeoutMiddleware } from '@venturekit/runtime';
const timeout = timeoutMiddleware(5000); // 5 seconds

Pass middleware in the handler config:

import { handler, corsMiddleware, timeoutMiddleware } from '@venturekit/runtime';
export const main = handler(async (_body, ctx, logger) => {
return { ok: true };
}, {
middleware: [
timeoutMiddleware(5000),
],
});

Built-in rate limiting with distributed DynamoDB storage (production) or in-memory (dev):

import { rateLimitMiddleware, createDefaultRateLimiter } from '@venturekit/runtime';
// Zero-config — 100 req/min per IP, auto-selects DynamoDB or memory
const rateLimit = rateLimitMiddleware({
check: await createDefaultRateLimiter(),
});
// Strict preset for auth endpoints — 20 req/min
const authRateLimit = rateLimitMiddleware({
check: await createDefaultRateLimiter({ preset: 'strict' }),
});

Presets:

PresetLimitWindowUse Case
relaxed200/min60sInternal APIs, dev
standard100/min60sPublic APIs (default)
strict20/min60sAuth, sensitive ops
api-key1000/min60sAuthenticated consumers

In production, rate limit counters are stored in DynamoDB with atomic increments and TTL cleanup. See the Lambda Invocation guide for more.

A middleware is an object with a name and fn:

import type { Middleware } from '@venturekit/runtime';
const myMiddleware: Middleware = {
name: 'myMiddleware',
fn: async (ctx, next) => {
// Run before handler
console.log('Before:', ctx.path);
// Call the next middleware or handler
const response = await next();
// Run after handler (optional)
return response;
},
};

Use compose() to combine multiple middleware into a single function:

import { compose } from '@venturekit/runtime';
const stack = compose([
loggingMiddleware(logger),
corsMiddleware({ /* ... */ }),
timeoutMiddleware(5000),
rateLimitMiddleware,
]);

Feature packages provide their own middleware:

import { createTenantMiddleware, createQuotaMiddleware } from '@venturekit-pro/tenancy';
export const main = handler(async (_body, ctx, logger) => {
// ctx.tenant is populated by tenant middleware
return { tenantId: ctx.tenant?.id };
}, {
scopes: ['api.read'],
middleware: [
createTenantMiddleware({ strategy: 'subdomain' }),
createQuotaMiddleware(),
],
});