# Middleware Documentation 0http-bun provides a comprehensive middleware system with built-in middlewares for common use cases. All middleware functions are TypeScript-ready and follow the standard middleware pattern. ## Dependency Installation ⚠️ **Important**: Starting with v1.2.2, middleware dependencies are now **optional** and must be installed separately when needed. This reduces the framework's footprint and improves startup performance through lazy loading. Install only the dependencies you need: ```bash # For JWT Authentication middleware bun install jose # For Logger middleware bun install pino # For Prometheus Metrics middleware bun install prom-client ``` **Benefits of Lazy Loading:** - 📦 **Smaller Bundle**: Only install what you use - ⚡ **Faster Startup**: Dependencies loaded only when middleware is used - 💾 **Lower Memory**: Reduced initial memory footprint - 🔧 **Better Control**: Explicit dependency management ## Table of Contents - [Middleware Pattern](#middleware-pattern) - [Built-in Middlewares](#built-in-middlewares) - [Body Parser](#body-parser) - [CORS](#cors) - [JWT Authentication](#jwt-authentication) - [Logger](#logger) - [Prometheus Metrics](#prometheus-metrics) - [Rate Limiting](#rate-limiting) - [Creating Custom Middleware](#creating-custom-middleware) ## Middleware Pattern All middlewares in 0http-bun follow the standard pattern: ```typescript import {ZeroRequest, StepFunction} from '0http-bun' type Middleware = ( req: ZeroRequest, next: StepFunction, ) => Promise | Response ``` ### TypeScript Support TypeScript type definitions are available for both the core framework and middleware modules: ```typescript // Core framework types (from root module) import {ZeroRequest, StepFunction, RequestHandler} from '0http-bun' // Middleware-specific types (from middleware module) import type { LoggerOptions, JWTAuthOptions, RateLimitOptions, RateLimitStore, MemoryStore, CORSOptions, BodyParserOptions, } from '0http-bun/lib/middleware' // Import middleware functions import { createLogger, createJWTAuth, createRateLimit, createPrometheusIntegration, } from '0http-bun/lib/middleware' ``` ## Built-in Middlewares All middleware can be imported from the main middleware module: ```javascript // Import all middleware from the middleware index const { createBodyParser, createCORS, createJWTAuth, createLogger, createRateLimit, createPrometheusMiddleware, createMetricsHandler, createPrometheusIntegration, } = require('0http-bun/lib/middleware') ``` For TypeScript: ```typescript // Import middleware functions import { createBodyParser, createCORS, createJWTAuth, createLogger, createRateLimit, createPrometheusMiddleware, createMetricsHandler, createPrometheusIntegration, } from '0http-bun/lib/middleware' // Import types import type { BodyParserOptions, CORSOptions, JWTAuthOptions, LoggerOptions, RateLimitOptions, } from '0http-bun/lib/middleware' ``` ### Body Parser Automatically parses request bodies based on Content-Type header. > ✅ **No additional dependencies required** - Uses Bun's built-in parsing capabilities. ```javascript const {createBodyParser} = require('0http-bun/lib/middleware') const router = http() // Basic usage router.use(createBodyParser()) // Access parsed body router.post('/api/data', (req) => { console.log(req.body) // Parsed body content return Response.json({received: req.body}) }) ``` **TypeScript Usage:** ```typescript import {createBodyParser} from '0http-bun/lib/middleware' import type {BodyParserOptions} from '0http-bun/lib/middleware' // With custom configuration const bodyParserOptions: BodyParserOptions = { json: { limit: 10 * 1024 * 1024, // 10MB strict: true, }, urlencoded: { extended: true, limit: 1024 * 1024, // 1MB }, } router.use(createBodyParser(bodyParserOptions)) ``` **Supported Content Types:** - `application/json` - Parsed as JSON (strict matching; `application/xml` and other `application/*` types are no longer routed to the JSON parser) - `application/x-www-form-urlencoded` - Parsed as form data - `multipart/form-data` - Parsed as FormData - `text/*` - Parsed as plain text - `application/octet-stream` - Parsed as ArrayBuffer **Security Features:** - **JSON nesting depth limit** (max 100) with string-aware scanning — brace characters inside JSON strings do not count toward the depth limit - **Prototype pollution protection** — multipart body and files objects use `Object.create(null)`, and dangerous property names (`__proto__`, `constructor`, `prototype`, etc.) are blocked - **Multipart filename sanitization** — strips `..`, path separators (`/`, `\`), null bytes, and leading dots. The original filename is preserved in `file.originalName` - **Size limit validation** — `parseLimit()` throws `TypeError` for unexpected types (e.g., `null`, `false`, objects) instead of silently defaulting to 1MB - **Custom `jsonParser` safety** — when a custom `jsonParser` function is provided, size limits are enforced before the parser is called - **Empty body handling** — empty or whitespace-only JSON bodies set `req.body` to `undefined` instead of silently returning `{}` - **Raw body via Symbol** — raw body text is stored via a Symbol (`RAW_BODY_SYMBOL`) instead of a public string property, preventing accidental serialization/logging **Accessing Raw Body:** ```javascript import {RAW_BODY_SYMBOL} from '0http-bun/lib/middleware/body-parser' router.post('/webhook', (req) => { const rawBody = req[RAW_BODY_SYMBOL] // Symbol.for('0http.rawBody') // Use rawBody for signature verification, etc. }) ``` **Verify Function:** When using the `verify` option with `createBodyParser`, the raw body is available via the same symbol: ```javascript const bodyParser = createBodyParser({ verify: (req, rawBody) => { // rawBody is the raw string before parsing const signature = req.headers.get('x-signature') if (!verifySignature(rawBody, signature)) { throw new Error('Invalid signature') } }, // Note: any JSON parsing behavior (such as deferNext) is handled internally when verify is set. }) ``` ### CORS Cross-Origin Resource Sharing middleware with flexible configuration. > ✅ **No additional dependencies required** - Built-in CORS implementation. ```javascript const {createCORS} = require('0http-bun/lib/middleware') // Basic usage (allows all origins) router.use(createCORS()) // Custom configuration router.use( createCORS({ origin: ['https://example.com', 'https://app.example.com'], methods: ['GET', 'POST', 'PUT', 'DELETE'], allowedHeaders: ['Content-Type', 'Authorization'], exposedHeaders: ['X-Total-Count'], credentials: true, maxAge: 86400, // Preflight cache duration (seconds) preflightContinue: false, optionsSuccessStatus: 204, }), ) // Dynamic origin validation // NOTE: null and missing origins are automatically rejected before calling the // validator function, preventing bypass via sandboxed iframes. router.use( createCORS({ origin: (origin, req) => { // Custom logic to validate origin // `origin` is guaranteed to be a non-null, non-'null' string here return ( origin?.endsWith('.mycompany.com') || origin === 'http://localhost:3000' ) }, }), ) ``` **Security behavior:** - When an origin is **not allowed**, CORS headers (methods, allowed headers, credentials, exposed headers) are **not set** on the response. Only `Vary: Origin` is added. - `null` origins (from sandboxed iframes, `file://` URLs, etc.) are **rejected** for array and function origin configurations. - `Vary: Origin` is set for all non-wildcard origin configurations to prevent CDN cache poisoning. - Wildcard (`*`) origins with `credentials: true` are blocked (CORS spec requirement). **TypeScript Usage:** ```typescript import {createCORS} from '0http-bun/lib/middleware' import type {CORSOptions} from '0http-bun/lib/middleware' const corsOptions: CORSOptions = { origin: ['https://example.com', 'https://app.example.com'], methods: ['GET', 'POST', 'PUT', 'DELETE'], credentials: true, } router.use(createCORS(corsOptions)) ``` ### JWT Authentication JSON Web Token authentication and authorization middleware with support for static secrets, JWKS endpoints, and API key authentication. > 📦 **Required dependency**: `bun install jose` #### Basic JWT with Static Secret ```javascript const {createJWTAuth} = require('0http-bun/lib/middleware') // Basic JWT verification with static secret router.use( '/api/protected/*', createJWTAuth({ secret: 'your-secret-key', algorithms: ['HS256'], }), ) ``` **TypeScript Usage:** ```typescript import {createJWTAuth} from '0http-bun/lib/middleware' import type {JWTAuthOptions} from '0http-bun/lib/middleware' const jwtOptions: JWTAuthOptions = { secret: 'your-secret-key', jwtOptions: { algorithms: ['HS256'], audience: 'your-api', issuer: 'your-service', }, } router.use('/api/protected/*', createJWTAuth(jwtOptions)) ``` #### JWT with JWKS URI (Recommended for Production) For production applications, especially when integrating with identity providers like Auth0, AWS Cognito, or Azure AD, use JWKS URI for automatic key rotation: ```typescript // Using JWKS URI (Auth0 example) router.use( '/api/protected/*', createJWTAuth({ jwksUri: 'https://your-domain.auth0.com/.well-known/jwks.json', algorithms: ['RS256'], issuer: 'https://your-domain.auth0.com/', audience: 'your-api-identifier', }), ) // AWS Cognito example router.use( '/api/protected/*', createJWTAuth({ jwksUri: 'https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json', algorithms: ['RS256'], issuer: 'https://cognito-idp.{region}.amazonaws.com/{userPoolId}', audience: 'your-client-id', }), ) // Azure AD example router.use( '/api/protected/*', createJWTAuth({ jwksUri: 'https://login.microsoftonline.com/{tenant}/discovery/v2.0/keys', algorithms: ['RS256'], issuer: 'https://login.microsoftonline.com/{tenant}/v2.0', audience: 'your-application-id', }), ) // Google Identity example router.use( '/api/protected/*', createJWTAuth({ jwksUri: 'https://www.googleapis.com/oauth2/v3/certs', algorithms: ['RS256'], issuer: 'https://accounts.google.com', audience: 'your-client-id.apps.googleusercontent.com', }), ) ``` #### Advanced Configuration ```typescript // Complete configuration example router.use( createJWTAuth({ // Option 1: Static secret (for development/simple cases) secret: process.env.JWT_SECRET, // Option 2: JWKS URI (recommended for production) jwksUri: process.env.JWKS_URI, // JWT verification options // IMPORTANT: Do NOT mix symmetric (HS*) and asymmetric (RS*/ES*/PS*) algorithms. // Use HS256 with static secrets, or RS256/ES256 with JWKS URIs. algorithms: ['HS256'], issuer: 'your-app', audience: 'your-users', clockTolerance: 10, // Clock skew tolerance (seconds) ignoreExpiration: false, ignoreNotBefore: false, // Custom token extraction getToken: (req) => { // Try multiple sources return ( req.headers.get('x-auth-token') || req.headers.get('authorization')?.replace('Bearer ', '') ) }, // Alternative token sources tokenHeader: 'x-custom-token', // Custom header name tokenQuery: 'access_token', // Query parameter name (see security note below) // Error handling onError: (err, req) => { console.error('JWT Error:', err) return Response.json( { error: 'Unauthorized', code: err.name, message: process.env.NODE_ENV === 'development' ? err.message : undefined, }, {status: 401}, ) }, // Custom unauthorized response unauthorizedResponse: (error, req) => { return Response.json( { error: 'Access denied', requestId: req.headers.get('x-request-id'), timestamp: new Date().toISOString(), }, {status: 401}, ) }, // Optional authentication (proceed even without token) // When optional: true, invalid tokens set req.ctx.authError and req.ctx.authAttempted = true optional: false, // Exclude certain paths (uses exact match or path boundary, NOT prefix matching) // e.g., '/health' excludes '/health' and '/health/...' but NOT '/healthcheck' excludePaths: ['/health', '/metrics', '/api/public'], // Token type validation (validates the JWT 'typ' header claim) // Case-insensitive comparison. Rejects tokens with missing or incorrect type. requiredTokenType: 'JWT', // or 'at+jwt' for access tokens, etc. }), ) ``` > **Security Note on `tokenQuery`:** Passing JWTs via query parameters exposes tokens in server logs, browser history, and HTTP `Referer` headers. Prefer header-based token extraction in production. #### API Key Authentication The JWT middleware also supports API key authentication as an alternative or fallback: ```typescript // API key with static keys router.use( '/api/*', createJWTAuth({ apiKeys: ['key1', 'key2', 'key3'], apiKeyHeader: 'x-api-key', // Default header }), ) // Access the raw API key (req.apiKey is masked for security) import {API_KEY_SYMBOL} from '0http-bun/lib/middleware/jwt-auth' router.get('/api/data', (req) => { console.log(req.apiKey) // 'xxxx****xxxx' (masked, safe for logging) console.log(req[API_KEY_SYMBOL]) // Raw API key via Symbol.for('0http.apiKey') }) // API key with custom validation router.use( '/api/*', createJWTAuth({ apiKeyValidator: async (apiKey, req) => { // Custom validation logic const user = await validateApiKeyInDatabase(apiKey) return user ? {id: user.id, name: user.name, apiKey} : false }, apiKeyHeader: 'x-api-key', }), ) // Combined JWT + API Key authentication router.use( '/api/*', createJWTAuth({ // JWT configuration jwksUri: process.env.JWKS_URI, algorithms: ['RS256'], // API Key fallback apiKeys: process.env.API_KEYS?.split(','), apiKeyHeader: 'x-api-key', // If JWT fails, try API key optional: false, }), ) ``` #### Environment-Based Configuration ```typescript // Dynamic configuration based on environment const jwtConfig = process.env.NODE_ENV === 'production' ? { // Production: Use JWKS for security and key rotation jwksUri: process.env.JWKS_URI, algorithms: ['RS256'], issuer: process.env.JWT_ISSUER, audience: process.env.JWT_AUDIENCE, } : { // Development: Use static secret for simplicity secret: process.env.JWT_SECRET || 'dev-secret-key', algorithms: ['HS256'], } router.use('/api/protected/*', createJWTAuth(jwtConfig)) ``` #### Access Decoded Token Data ```typescript // Access decoded token in route handlers router.get('/api/profile', (req) => { // Decoded JWT payload console.log(req.user) // Decoded JWT payload console.log(req.ctx.user) // Same as req.user // JWT header and payload (raw token is NOT included for security) console.log(req.jwt) // { payload, header } console.log(req.ctx.jwt) // Same as req.jwt // API key authentication data (if used) console.log(req.apiKey) // Masked API key (xxxx****xxxx) console.log(req.ctx.apiKey) // Same as req.apiKey (masked) // Optional mode: check if auth was attempted but failed if (req.ctx.authAttempted && req.ctx.authError) { console.log('Auth failed:', req.ctx.authError) } return Response.json({ user: req.user, tokenInfo: { issuer: req.jwt?.payload.iss, audience: req.jwt?.payload.aud, expiresAt: new Date(req.jwt?.payload.exp * 1000), issuedAt: new Date(req.jwt?.payload.iat * 1000), }, }) }) ``` ### Logger Request logging middleware with customizable output formats. > 📦 **Required dependency for structured logging**: `bun install pino` > ✅ **Simple logger** (`simpleLogger`) has no dependencies - uses `console.log` ```javascript const {createLogger, simpleLogger} = require('0http-bun/lib/middleware') // Simple logging router.use(simpleLogger()) // Detailed logging with custom format router.use( createLogger({ pinoOptions: { level: 'info', transport: { target: 'pino-pretty', options: {colorize: true}, }, }, logBody: false, excludePaths: ['/health', '/metrics'], }), ) ``` **TypeScript Usage:** ```typescript import {createLogger, simpleLogger} from '0http-bun/lib/middleware' import type {LoggerOptions} from '0http-bun/lib/middleware' const loggerOptions: LoggerOptions = { pinoOptions: { level: 'info', transport: { target: 'pino-pretty', options: {colorize: true}, }, }, logBody: true, excludePaths: ['/health', '/ping'], serializers: { req: (req) => ({ method: req.method, url: req.url, userAgent: req.headers.get('user-agent'), }), }, } router.use(createLogger(loggerOptions)) ``` **Available Formats:** - `combined` - Apache Combined Log Format - `common` - Apache Common Log Format - `short` - Shorter than common, includes response time - `tiny` - Minimal output - `dev` - Development-friendly colored output ### Prometheus Metrics Comprehensive Prometheus metrics integration for monitoring and observability with built-in security and performance optimizations. > 📦 **Required dependency**: `bun install prom-client` ```javascript const { createPrometheusMiddleware, createMetricsHandler, createPrometheusIntegration, } = require('0http-bun/lib/middleware') // Simple setup with default metrics const prometheus = createPrometheusIntegration() router.use(prometheus.middleware) router.get('/metrics', prometheus.metricsHandler) ``` #### Default Metrics Collected The Prometheus middleware automatically collects: - **HTTP Request Duration** - Histogram of request durations in seconds (buckets: 0.001, 0.005, 0.015, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 1, 2, 5, 10) - **HTTP Request Count** - Counter of total requests by method, route, and status - **HTTP Request Size** - Histogram of request body sizes (buckets: 1B, 10B, 100B, 1KB, 10KB, 100KB, 1MB, 10MB) - **HTTP Response Size** - Histogram of response body sizes (buckets: 1B, 10B, 100B, 1KB, 10KB, 100KB, 1MB, 10MB) - **Active Connections** - Gauge of currently active HTTP connections - **Node.js Metrics** - Memory usage, CPU, garbage collection (custom buckets), event loop lag (5ms precision) #### Advanced Configuration ```javascript const { createPrometheusMiddleware, createMetricsHandler, createPrometheusIntegration, } = require('0http-bun/lib/middleware') const prometheus = createPrometheusIntegration({ // Control default Node.js metrics collection collectDefaultMetrics: true, // Exclude paths from metrics collection (optimized for performance) excludePaths: ['/health', '/ping', '/favicon.ico'], // Skip certain HTTP methods skipMethods: ['OPTIONS'], // Custom route normalization with security controls normalizeRoute: (req) => { const url = new URL(req.url, 'http://localhost') return url.pathname .replace(/\/users\/\d+/, '/users/:id') .replace(/\/api\/v\d+/, '/api/:version') }, // Add custom labels with automatic sanitization extractLabels: (req, response) => { return { user_type: req.headers.get('x-user-type') || 'anonymous', api_version: req.headers.get('x-api-version') || 'v1', } }, // Use custom metrics object instead of default metrics metrics: customMetricsObject, }) ``` #### Custom Business Metrics ```javascript const {createPrometheusIntegration} = require('0http-bun/lib/middleware') // Get the prometheus client from the integration const prometheus = createPrometheusIntegration() const {promClient} = prometheus // Create custom metrics const orderCounter = new promClient.Counter({ name: 'orders_total', help: 'Total number of orders processed', labelNames: ['status', 'payment_method'], }) const orderValue = new promClient.Histogram({ name: 'order_value_dollars', help: 'Value of orders in dollars', labelNames: ['payment_method'], buckets: [10, 50, 100, 500, 1000, 5000], }) // Use in your routes router.post('/orders', async (req) => { const order = await processOrder(req.body) // Record custom metrics orderCounter.inc({ status: order.status, payment_method: order.payment_method, }) if (order.status === 'completed') { orderValue.observe( { payment_method: order.payment_method, }, order.amount, ) } return Response.json(order) }) ``` #### Metrics Endpoint Options ```javascript const {createMetricsHandler} = require('0http-bun/lib/middleware') // Custom metrics endpoint const metricsHandler = createMetricsHandler({ endpoint: '/custom-metrics', // Default: '/metrics' registry: customRegistry, // Default: promClient.register }) router.get('/custom-metrics', metricsHandler) ``` #### Route Normalization & Security The middleware automatically normalizes routes and implements security measures to prevent high cardinality and potential attacks: ```javascript // URLs like these: // /users/123, /users/456, /users/789 // Are normalized to: /users/:id // /products/abc-123, /products/def-456 // Are normalized to: /products/:slug // /api/v1/data, /api/v2/data // Are normalized to: /api/:version/data // Route sanitization examples: // /users/:id → _users__id (special characters replaced with underscores) // /api/v1/orders → _api_v1_orders // Very long tokens → _api__token (pattern-based normalization) ``` **Route Sanitization:** - Special characters (`/`, `:`, etc.) are replaced with underscores (`_`) for Prometheus compatibility - UUIDs are automatically normalized to `:id` patterns - Long tokens (>20 characters) are normalized to `:token` patterns - Numeric IDs are normalized to `:id` patterns - Route complexity is limited to 10 segments maximum **Security Features:** - **Label Sanitization**: Removes potentially dangerous characters from metric labels and truncates values to 100 characters - **Cardinality Limits**: Prevents memory exhaustion from too many unique metric combinations - **Route Complexity Limits**: Caps the number of route segments to 10 to prevent DoS attacks - **Size Limits**: Limits request/response body size processing (up to 100MB) to prevent memory issues - **Header Processing Limits**: Caps the number of headers processed per request (50 for requests, 20 for responses) - **URL Processing**: Handles both full URLs and pathname-only URLs with proper fallback handling #### Performance Optimizations - **Fast Path for Excluded Routes**: Bypasses all metric collection for excluded paths with smart URL parsing - **Lazy Evaluation**: Only processes metrics when actually needed - **Efficient Size Calculation**: Optimized request/response size measurement with capping at 1MB estimation - **Error Handling**: Graceful handling of malformed URLs and invalid data with fallback mechanisms - **Header Count Limits**: Prevents excessive header processing overhead (50 request headers, 20 response headers) - **Smart URL Parsing**: Handles both full URLs and pathname-only URLs efficiently #### Production Considerations - **Performance**: Adds <1ms overhead per request with optimized fast paths - **Memory**: Metrics stored in memory with cardinality controls; use recording rules for high cardinality - **Security**: Built-in protections against label injection and cardinality bombs - **Cardinality**: Automatic limits prevent high cardinality issues - **Monitoring**: Consider protecting `/metrics` endpoint in production #### Integration with Monitoring ```yaml # prometheus.yml scrape_configs: - job_name: '0http-bun-app' static_configs: - targets: ['localhost:3000'] scrape_interval: 15s metrics_path: /metrics ``` #### Troubleshooting **Common Issues:** - **High Memory Usage**: Check for high cardinality metrics. Route patterns should be normalized (e.g., `/users/:id` not `/users/12345`) - **Missing Metrics**: Ensure paths aren't in `excludePaths` and HTTP methods aren't in `skipMethods` - **Route Sanitization**: Routes are automatically sanitized (special characters become underscores: `/users/:id` → `_users__id`) - **URL Parsing Errors**: The middleware handles both full URLs and pathname-only URLs with graceful fallback **Performance Tips:** - Use `excludePaths` for health checks and static assets - Consider using `skipMethods` for OPTIONS requests - Monitor memory usage in production for metric cardinality - Use Prometheus recording rules for high-cardinality aggregations ### Rate Limiting Configurable rate limiting middleware with multiple store options. > ✅ **No additional dependencies required** - Uses built-in memory store. ```javascript const {createRateLimit, MemoryStore} = require('0http-bun/lib/middleware') // Basic rate limiting router.use( createRateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // Max 100 requests per windowMs }), ) // Advanced configuration router.use( createRateLimit({ windowMs: 60 * 1000, // 1 minute max: 20, // Max requests keyGenerator: (req) => { // Custom key generation // Default uses: req.ip || req.remoteAddress || req.socket?.remoteAddress || 'unknown' // NOTE: Proxy headers are NOT trusted by default. If behind a // reverse proxy, you MUST provide a custom keyGenerator: return ( req.headers.get('x-user-id') || req.headers.get('x-forwarded-for')?.split(',')[0]?.trim() || req.ip || 'unknown' ) }, skip: (req) => { // Skip rate limiting for certain requests return req.url.startsWith('/health') }, handler: (req, totalHits, max, resetTime) => { // Custom rate limit exceeded response return Response.json( { error: 'Rate limit exceeded', resetTime: resetTime.toISOString(), retryAfter: Math.ceil((resetTime.getTime() - Date.now()) / 1000), }, {status: 429}, ) }, standardHeaders: true, // Send X-RateLimit-* headers // standardHeaders options: // true - full headers (X-RateLimit-Limit, Remaining, Reset, Used) — default // false - no rate limit headers // 'minimal' - only Retry-After on 429 responses (hides exact config/counters) // excludePaths uses exact match or boundary matching (NOT prefix) // e.g., '/health' matches '/health' and '/health/...' but NOT '/healthcheck' excludePaths: ['/health', '/metrics'], }), ) // Custom store (for distributed systems) router.use( createRateLimit({ store: new MemoryStore(), // Built-in memory store // Or implement custom store with increment() method // Note: The store is always the constructor-configured instance. // It cannot be overridden at runtime via the request object. }), ) ``` **TypeScript Usage:** ```typescript import {createRateLimit, MemoryStore} from '0http-bun/lib/middleware' import type {RateLimitOptions, RateLimitStore} from '0http-bun/lib/middleware' const rateLimitOptions: RateLimitOptions = { windowMs: 15 * 60 * 1000, // 15 minutes max: 100, keyGenerator: (req) => { // Default: req.ip || req.remoteAddress || req.socket?.remoteAddress || 'unknown' // Custom: read from proxy-set header if behind a reverse proxy return ( req.headers.get('x-forwarded-for')?.split(',')[0]?.trim() || req.ip || 'unknown' ) }, standardHeaders: true, excludePaths: ['/health', '/ping'], } router.use(createRateLimit(rateLimitOptions)) // Custom store implementation class CustomStore implements RateLimitStore { // Note: For MemoryStore, increment is synchronous to prevent TOCTOU races. // Custom stores may be async if using external backends (e.g., Redis). async increment( key: string, windowMs: number, ): Promise<{totalHits: number; resetTime: Date}> { // Custom implementation return {totalHits: 1, resetTime: new Date(Date.now() + windowMs)} } } ``` #### Sliding Window Rate Limiter For more precise rate limiting, use the sliding window implementation that **prevents burst traffic** at any point in time: ```javascript const {createSlidingWindowRateLimit} = require('0http-bun/lib/middleware') // Basic sliding window rate limiter router.use( createSlidingWindowRateLimit({ windowMs: 60 * 1000, // 1 minute sliding window max: 10, // Max 10 requests per minute maxKeys: 10000, // Maximum tracked keys (default: 10000) — prevents unbounded memory growth keyGenerator: (req) => req.ip || req.remoteAddress || req.socket?.remoteAddress || 'unknown', }), ) ``` > **Memory safety:** The sliding window rate limiter enforces a `maxKeys` limit (default: 10,000). When the limit is exceeded, the oldest entry is evicted. A periodic cleanup interval runs at most every 60 seconds (or `windowMs`, whichever is shorter) and uses `unref()` so it doesn't prevent process exit. **TypeScript Usage:** ```typescript import {createSlidingWindowRateLimit} from '0http-bun/lib/middleware' import type {RateLimitOptions} from '0http-bun/lib/middleware' const slidingOptions: RateLimitOptions = { windowMs: 60 * 1000, // 1 minute max: 10, // 10 requests max maxKeys: 10000, // Max tracked keys (default: 10000) keyGenerator: (req) => req.user?.id || req.ip || 'unknown', handler: (req, hits, max, resetTime) => { return Response.json( { error: 'Rate limit exceeded', retryAfter: Math.ceil((resetTime.getTime() - Date.now()) / 1000), limit: max, used: hits, }, {status: 429}, ) }, } router.use(createSlidingWindowRateLimit(slidingOptions)) ``` **How Sliding Window Differs from Fixed Window:** The sliding window approach provides **more accurate and fair rate limiting** by tracking individual request timestamps: - **Fixed Window**: Divides time into discrete chunks (e.g., 09:00:00-09:00:59, 09:01:00-09:01:59) - ⚠️ **Problem**: Allows burst traffic at window boundaries (20 requests in 2 seconds) - **Sliding Window**: Uses a continuous, moving time window from current moment - ✅ **Advantage**: Prevents bursts at any point in time (true rate limiting) **Use Cases for Sliding Window:** ```javascript // Financial API - Zero tolerance for payment bursts router.use( '/api/payments/*', createSlidingWindowRateLimit({ windowMs: 60 * 1000, // 1 minute max: 3, // Only 3 payment attempts per minute keyGenerator: (req) => req.user.accountId, }), ) // User Registration - Prevent automated signups router.use( '/api/register', createSlidingWindowRateLimit({ windowMs: 3600 * 1000, // 1 hour max: 3, // 3 accounts per IP per hour keyGenerator: (req) => req.ip || req.remoteAddress || req.socket?.remoteAddress || 'unknown', }), ) // File Upload - Prevent abuse router.use( '/api/upload', createSlidingWindowRateLimit({ windowMs: 300 * 1000, // 5 minutes max: 10, // 10 uploads per 5 minutes keyGenerator: (req) => req.user.id, }), ) ``` **Performance Considerations:** - **Memory Usage**: Higher than fixed window (stores timestamp arrays), bounded by `maxKeys` (default: 10,000) - **Time Complexity**: O(n) per request where n = requests in window - **Cleanup**: Automatic periodic cleanup runs at most every 60 seconds; interval uses `unref()` to not prevent process exit - **Best For**: Critical APIs, financial transactions, user-facing features - **Use Fixed Window For**: High-volume APIs where approximate limiting is acceptable **Advanced Configuration:** ```typescript // Tiered rate limiting based on user level const createTieredRateLimit = (req) => { const userTier = req.user?.tier || 'free' const configs = { free: {windowMs: 60 * 1000, max: 10}, premium: {windowMs: 60 * 1000, max: 100}, enterprise: {windowMs: 60 * 1000, max: 1000}, } return createSlidingWindowRateLimit(configs[userTier]) } ``` **Rate Limit Headers:** Both rate limiters send the following headers when `standardHeaders: true`: - `X-RateLimit-Limit` - Request limit - `X-RateLimit-Remaining` - Remaining requests - `X-RateLimit-Reset` - Reset time (Unix timestamp) - `X-RateLimit-Used` - Used requests When `standardHeaders: 'minimal'`, only `Retry-After` is sent on 429 responses. This prevents disclosing exact rate limit configuration and usage counters to clients. **Error Handling:** Rate limiting middleware allows errors to bubble up as proper HTTP 500 responses. If your `keyGenerator` function or custom `store.increment()` method throws an error, it will not be caught and masked - the error will propagate up the middleware chain for proper error handling. ## Creating Custom Middleware ### Basic Middleware ```typescript import {ZeroRequest, StepFunction} from '0http-bun' const customMiddleware = (req: ZeroRequest, next: StepFunction) => { // Pre-processing req.ctx = req.ctx || {} req.ctx.startTime = Date.now() // Continue to next middleware/handler const response = next() // Post-processing (if needed) return response } router.use(customMiddleware) ``` ### Async Middleware ```typescript const asyncMiddleware = async (req: ZeroRequest, next: StepFunction) => { // Async pre-processing const user = await validateUserSession(req) req.ctx = {user} // Continue const response = await next() // Async post-processing await logUserActivity(user, req.url) return response } ``` ### Error Handling in Middleware ```typescript const errorHandlingMiddleware = async ( req: ZeroRequest, next: StepFunction, ) => { try { return await next() } catch (error) { console.error('Middleware error:', error) // Return error response return Response.json( { error: 'Internal server error', message: process.env.NODE_ENV === 'development' ? error.message : undefined, }, {status: 500}, ) } } ``` ## Middleware Execution Order Middlewares execute in the order they are registered: ```typescript router.use(middleware1) // Executes first router.use(middleware2) // Executes second router.use(middleware3) // Executes third router.get('/test', handler) // Final handler ``` ## Path-Specific Middleware Apply middleware only to specific paths: ```typescript // API-only middleware router.use('/api/*', createJWTAuth({secret: 'api-secret'})) router.use('/api/*', createRateLimit({max: 1000})) // Admin-only middleware router.use('/admin/*', adminAuthMiddleware) router.use('/admin/*', auditLogMiddleware) // Public paths (no auth required) router.get('/health', healthCheckHandler) router.get('/metrics', metricsHandler) ``` ## Best Practices 1. **Order Matters**: Place security middleware (CORS, auth) before business logic 2. **Error Handling**: Always handle errors in async middleware 3. **Performance**: Use `skip` functions to avoid unnecessary processing 4. **Context**: Use `req.ctx` to pass data between middlewares 5. **Immutability**: Don't modify the original request object directly 6. **Logging**: Log middleware errors for debugging 7. **Testing**: Test middleware in isolation with mock requests ## Examples ### Complete Middleware Stack ```typescript const { createCORS, createLogger, createBodyParser, createJWTAuth, createRateLimit, } = require('0http-bun/lib/middleware') const router = http() // 1. CORS (handle preflight requests first) router.use( createCORS({ origin: process.env.ALLOWED_ORIGINS?.split(','), credentials: true, }), ) // 2. Logging (log all requests) router.use(createLogger({format: 'combined'})) // 3. Rate limiting (protect against abuse) // NOTE: Default key generator uses req.ip — provide keyGenerator if behind a proxy router.use( createRateLimit({ windowMs: 15 * 60 * 1000, max: 1000, // keyGenerator: (req) => req.headers.get('x-forwarded-for')?.split(',')[0]?.trim() || req.ip || 'unknown', }), ) // 4. Body parsing (parse request bodies) router.use(createBodyParser({limit: '10mb'})) // 5. Authentication (protect API routes) // Default algorithms: ['HS256'] — specify ['RS256'] for asymmetric keys router.use( '/api/*', createJWTAuth({ secret: process.env.JWT_SECRET, }), ) // Routes router.get('/api/public/status', () => Response.json({status: 'ok'})) router.get('/api/protected/data', (req) => Response.json({user: req.user})) ``` ## Dependency Summary For your convenience, here's a quick reference of which dependencies you need to install for each middleware: | Middleware | Dependencies Required | Install Command | | ----------------------- | --------------------- | ------------------------- | | **Body Parser** | ✅ None | Built-in | | **CORS** | ✅ None | Built-in | | **Rate Limiting** | ✅ None | Built-in | | **Logger** (simple) | ✅ None | Built-in | | **Logger** (structured) | 📦 `pino` | `bun install pino` | | **JWT Authentication** | 📦 `jose` | `bun install jose` | | **Prometheus Metrics** | 📦 `prom-client` | `bun install prom-client` | **Install all optional dependencies at once:** ```bash bun install pino jose prom-client ``` This middleware stack provides a solid foundation for most web applications with security, logging, and performance features built-in.