Middleware API
Complete API reference for Verb's middleware system including built-in middleware and custom middleware creation.
Middleware Types
Basic Middleware
typescript
type MiddlewareHandler = (
req: VerbRequest,
res: VerbResponse,
next: NextFunction
) => void | Promise<void>;
Error Middleware
typescript
type ErrorHandler = (
error: Error,
req: VerbRequest,
res: VerbResponse,
next: NextFunction
) => void | Promise<void>;
Next Function
typescript
type NextFunction = (error?: Error) => void;
Built-in Middleware
middleware.json(options?)
Parse JSON request bodies.
typescript
middleware.json(options?: JsonOptions): MiddlewareHandler
Options:
typescript
interface JsonOptions {
limit?: string | number; // Size limit (default: "100kb")
strict?: boolean; // Strict JSON parsing (default: true)
type?: string | string[]; // Content-Type to parse (default: "application/json")
verify?: (req: Request, body: string) => void; // Verification function
}
Example:
typescript
app.use(middleware.json({
limit: "50mb",
strict: false,
type: ["application/json", "text/json"]
}));
middleware.urlencoded(options?)
Parse URL-encoded request bodies.
typescript
middleware.urlencoded(options?: UrlEncodedOptions): MiddlewareHandler
Options:
typescript
interface UrlEncodedOptions {
limit?: string | number; // Size limit
extended?: boolean; // Use qs library (default: false)
parameterLimit?: number; // Max parameters (default: 1000)
type?: string | string[]; // Content-Type to parse
}
Example:
typescript
app.use(middleware.urlencoded({
extended: true,
limit: "10mb"
}));
middleware.cors(options?)
Enable Cross-Origin Resource Sharing.
typescript
middleware.cors(options?: CorsOptions): MiddlewareHandler
Options:
typescript
interface CorsOptions {
origin?: string | string[] | boolean | ((origin: string) => boolean);
methods?: string | string[];
allowedHeaders?: string | string[];
exposedHeaders?: string | string[];
credentials?: boolean;
maxAge?: number;
preflightContinue?: boolean;
optionsSuccessStatus?: number;
}
Example:
typescript
app.use(middleware.cors({
origin: ["https://example.com", "https://app.example.com"],
credentials: true,
methods: ["GET", "POST", "PUT", "DELETE"],
allowedHeaders: ["Content-Type", "Authorization"]
}));
middleware.staticFiles(root, options?)
Serve static files.
typescript
middleware.staticFiles(root: string, options?: StaticOptions): MiddlewareHandler
Options:
typescript
interface StaticOptions {
maxAge?: number; // Cache max-age in ms
immutable?: boolean; // Cache-Control immutable
fallthrough?: boolean; // Fall through to next middleware
index?: string | false; // Directory index file
extensions?: string[]; // File extensions to try
dotfiles?: "allow" | "deny" | "ignore";
etag?: boolean; // Generate ETags
lastModified?: boolean; // Set Last-Modified header
}
Example:
typescript
app.use("/public", middleware.staticFiles("./public", {
maxAge: 86400000, // 1 day
etag: true,
extensions: ["html", "htm"]
}));
middleware.compression(options?)
Compress response bodies.
typescript
middleware.compression(options?: CompressionOptions): MiddlewareHandler
Options:
typescript
interface CompressionOptions {
threshold?: number; // Minimum size to compress
level?: number; // Compression level (1-9)
filter?: (req: Request, res: Response) => boolean;
chunkSize?: number; // Chunk size for streaming
}
Example:
typescript
app.use(middleware.compression({
threshold: 1024,
level: 6,
filter: (req, res) => {
// Don't compress images
return !req.path.match(/\.(jpg|jpeg|png|gif)$/);
}
}));
middleware.rateLimit(options)
Rate limiting middleware.
typescript
middleware.rateLimit(options: RateLimitOptions): MiddlewareHandler
Options:
typescript
interface RateLimitOptions {
windowMs: number; // Time window in ms
max: number; // Max requests per window
message?: string; // Error message
statusCode?: number; // Error status code
headers?: boolean; // Include rate limit headers
keyGenerator?: (req: Request) => string;
skip?: (req: Request) => boolean;
onLimitReached?: (req: Request) => void;
}
Example:
typescript
app.use("/api", middleware.rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100,
message: "Too many requests",
keyGenerator: (req) => req.ip
}));
middleware.helmet(options?)
Security headers middleware.
typescript
middleware.helmet(options?: HelmetOptions): MiddlewareHandler
Options:
typescript
interface HelmetOptions {
contentSecurityPolicy?: CSPOptions | false;
crossOriginEmbedderPolicy?: boolean;
crossOriginOpenerPolicy?: boolean;
crossOriginResourcePolicy?: boolean;
dnsPrefetchControl?: boolean;
frameguard?: FrameguardOptions | false;
hidePoweredBy?: boolean;
hsts?: HSTSOptions | false;
ieNoOpen?: boolean;
noSniff?: boolean;
originAgentCluster?: boolean;
permittedCrossDomainPolicies?: boolean;
referrerPolicy?: ReferrerPolicyOptions;
xssFilter?: boolean;
}
Example:
typescript
app.use(middleware.helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'"]
}
},
hsts: {
maxAge: 31536000,
includeSubDomains: true
}
}));
middleware.logger(format?, options?)
Request logging middleware.
typescript
middleware.logger(format?: string, options?: LoggerOptions): MiddlewareHandler
Formats:
"combined"
- Apache combined log format"common"
- Apache common log format"dev"
- Concise colored output for development"short"
- Shorter than default"tiny"
- Minimal output
Options:
typescript
interface LoggerOptions {
skip?: (req: Request, res: Response) => boolean;
stream?: WritableStream;
immediate?: boolean;
}
Example:
typescript
app.use(middleware.logger("combined", {
skip: (req) => req.path.startsWith("/health")
}));
middleware.session(options)
Session management middleware.
typescript
middleware.session(options: SessionOptions): MiddlewareHandler
Options:
typescript
interface SessionOptions {
secret: string | string[];
name?: string;
resave?: boolean;
saveUninitialized?: boolean;
cookie?: CookieOptions;
store?: SessionStore;
genid?: () => string;
rolling?: boolean;
proxy?: boolean;
}
Example:
typescript
app.use(middleware.session({
secret: "your-secret-key",
name: "sessionId",
cookie: {
secure: true,
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000 // 1 day
}
}));
middleware.multipart(options?)
Parse multipart/form-data.
typescript
middleware.multipart(options?: MultipartOptions): MiddlewareHandler
Options:
typescript
interface MultipartOptions {
maxFiles?: number; // Max number of files
maxFileSize?: number; // Max file size in bytes
maxFieldSize?: number; // Max field size in bytes
allowedTypes?: string[]; // Allowed MIME types
uploadDir?: string; // Upload directory
keepExtensions?: boolean; // Keep file extensions
}
Example:
typescript
app.use(middleware.multipart({
maxFiles: 5,
maxFileSize: 10 * 1024 * 1024, // 10MB
allowedTypes: ["image/*", "application/pdf"],
uploadDir: "./uploads"
}));
Custom Middleware
Basic Middleware
typescript
const customMiddleware = (req, res, next) => {
// Add custom property
req.customData = { timestamp: Date.now() };
// Modify response
res.setHeader("X-Custom-Header", "value");
// Continue to next middleware
next();
};
app.use(customMiddleware);
Async Middleware
typescript
const asyncMiddleware = async (req, res, next) => {
try {
const data = await fetchSomeData();
req.data = data;
next();
} catch (error) {
next(error); // Pass error to error handler
}
};
app.use(asyncMiddleware);
Conditional Middleware
typescript
const conditionalMiddleware = (condition) => {
return (req, res, next) => {
if (condition(req)) {
// Apply middleware logic
req.conditional = true;
}
next();
};
};
app.use(conditionalMiddleware((req) => req.path.startsWith("/api")));
Factory Middleware
typescript
const createAuthMiddleware = (options = {}) => {
const { secret, algorithms = ["HS256"] } = options;
return async (req, res, next) => {
try {
const token = req.headers.get("authorization")?.replace("Bearer ", "");
if (!token) {
return res.status(401).json({ error: "No token provided" });
}
const decoded = jwt.verify(token, secret, { algorithms });
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ error: "Invalid token" });
}
};
};
app.use("/api/protected", createAuthMiddleware({
secret: process.env.JWT_SECRET
}));
Error Middleware
Basic Error Handler
typescript
const errorHandler = (error, req, res, next) => {
console.error("Error:", error);
res.status(error.status || 500).json({
error: error.message || "Internal Server Error",
...(process.env.NODE_ENV === "development" && { stack: error.stack })
});
};
// Error middleware must be last
app.use(errorHandler);
Typed Error Handler
typescript
class HttpError extends Error {
constructor(public status: number, message: string, public code?: string) {
super(message);
this.name = "HttpError";
}
}
const typedErrorHandler = (error, req, res, next) => {
if (error instanceof HttpError) {
return res.status(error.status).json({
error: error.message,
code: error.code
});
}
if (error.name === "ValidationError") {
return res.status(400).json({
error: "Validation failed",
details: error.details
});
}
// Default error
res.status(500).json({ error: "Internal Server Error" });
};
app.use(typedErrorHandler);
404 Handler
typescript
const notFoundHandler = (req, res) => {
res.status(404).json({
error: "Not Found",
message: `Cannot ${req.method} ${req.path}`,
timestamp: new Date().toISOString()
});
};
// 404 handler should be after all routes but before error handler
app.use(notFoundHandler);
Middleware Composition
Combining Middleware
typescript
const authAndLog = [
middleware.logger("dev"),
authenticate,
authorize("admin")
];
app.get("/admin/users", ...authAndLog, getUsersHandler);
Middleware Pipeline
typescript
class MiddlewarePipeline {
constructor() {
this.middleware = [];
}
use(fn) {
this.middleware.push(fn);
return this;
}
execute() {
return (req, res, next) => {
let index = 0;
const dispatch = (i) => {
if (i >= this.middleware.length) return next();
const fn = this.middleware[i];
try {
fn(req, res, () => dispatch(i + 1));
} catch (error) {
next(error);
}
};
dispatch(0);
};
}
}
const pipeline = new MiddlewarePipeline()
.use(middleware.logger("dev"))
.use(authenticate)
.use(authorize("admin"));
app.use("/admin", pipeline.execute());
Middleware Context
Sharing Data
typescript
// Add data to request
const addContextMiddleware = (req, res, next) => {
req.context = {
requestId: generateId(),
startTime: Date.now(),
user: null
};
next();
};
// Use data in later middleware
const useContextMiddleware = (req, res, next) => {
console.log(`Request ${req.context.requestId} processing`);
next();
};
Response Helpers
typescript
const responseHelpersMiddleware = (req, res, next) => {
res.success = (data, message = "Success") => {
return res.json({ success: true, data, message });
};
res.error = (message, status = 400, code = null) => {
return res.status(status).json({ success: false, error: message, code });
};
res.paginate = (data, page, limit, total) => {
return res.json({
data,
pagination: {
page,
limit,
total,
pages: Math.ceil(total / limit)
}
});
};
next();
};
app.use(responseHelpersMiddleware);
Testing Middleware
typescript
import { test, expect } from "bun:test";
test("custom middleware", async () => {
const app = createServer();
const testMiddleware = (req, res, next) => {
req.testValue = "middleware-value";
next();
};
app.use(testMiddleware);
app.get("/test", (req, res) => {
res.json({ value: req.testValue });
});
const handler = app.createFetchHandler();
const response = await handler(new Request("http://localhost/test"));
const data = await response.json();
expect(data.value).toBe("middleware-value");
});
test("error middleware", async () => {
const app = createServer();
app.get("/error", (req, res) => {
throw new Error("Test error");
});
app.use((error, req, res, next) => {
res.status(500).json({ error: error.message });
});
const handler = app.createFetchHandler();
const response = await handler(new Request("http://localhost/error"));
expect(response.status).toBe(500);
const data = await response.json();
expect(data.error).toBe("Test error");
});
Performance Considerations
Middleware Order
typescript
// Correct order for performance
app.use(middleware.compression()); // First - compress early
app.use(middleware.staticFiles()); // Second - serve static files
app.use(middleware.cors()); // Third - CORS headers
app.use(middleware.json()); // Fourth - parse bodies
app.use(middleware.logger()); // Fifth - log requests
// Routes
app.get("/api/users", handler);
// Error handling last
app.use(errorHandler);
Conditional Application
typescript
// Only apply heavy middleware when needed
app.use("/api", middleware.json()); // Only for API routes
app.use("/upload", middleware.multipart()); // Only for upload routes
app.use("/admin", authenticate); // Only for admin routes
Async Considerations
typescript
// Wrap async middleware to handle rejections
const asyncHandler = (fn) => {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
};
app.use(asyncHandler(async (req, res, next) => {
req.data = await fetchData();
next();
}));
Common Patterns
Authentication Middleware
typescript
const authenticate = async (req, res, next) => {
try {
const token = req.headers.get("authorization")?.replace("Bearer ", "");
if (!token) {
return res.status(401).json({ error: "No token provided" });
}
const user = await verifyToken(token);
req.user = user;
next();
} catch (error) {
res.status(401).json({ error: "Invalid token" });
}
};
Authorization Middleware
typescript
const authorize = (roles) => {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: "Authentication required" });
}
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: "Insufficient permissions" });
}
next();
};
};
Validation Middleware
typescript
const validate = (schema) => {
return (req, res, next) => {
try {
req.body = schema.parse(req.body);
next();
} catch (error) {
res.status(400).json({
error: "Validation failed",
details: error.errors
});
}
};
};
Type Definitions
typescript
interface MiddlewareHandler {
(req: VerbRequest, res: VerbResponse, next: NextFunction): void | Promise<void>;
}
interface ErrorHandler {
(error: Error, req: VerbRequest, res: VerbResponse, next: NextFunction): void | Promise<void>;
}
interface NextFunction {
(error?: Error): void;
}
interface MiddlewareOptions {
[key: string]: any;
}