libxev-http provides a powerful and flexible middleware system that allows you to process HTTP requests and responses in a pipeline fashion. This document explains how to use and create middleware.
The middleware system in libxev-http supports:
- Global middleware: Applied to all routes
- Route-specific middleware: Applied only to specific routes
- Built-in middleware: Common functionality like logging, CORS, authentication, etc.
- Custom middleware: Create your own middleware functions
- Middleware chaining: Multiple middleware can be chained together
pub const MiddlewareFn = *const fn (*Context, NextFn) anyerror!void;
pub const NextFn = *const fn (*Context) anyerror!void;A middleware function receives:
ctx: The HTTP context containing request and response datanext: A function to call the next middleware in the chain
- Global middleware (in registration order)
- Route-specific middleware (in registration order)
- Route handler
Global middleware applies to all routes:
var server = try libxev_http.createServer(allocator, "127.0.0.1", 8080);
// Add global middleware
try server.use("logging", libxev_http.loggingMiddleware);
try server.use("request-id", libxev_http.requestIdMiddleware);
try server.use("cors", libxev_http.corsMiddleware);Route-specific middleware applies only to that route:
// Create a route
const protected_route = try server.get("/api/protected", protectedHandler);
// Add middleware to this specific route
try protected_route.use("auth", libxev_http.basicAuthMiddleware);
try protected_route.use("rate-limit", libxev_http.rateLimitMiddleware);libxev-http provides several built-in middleware functions:
Logs request details and timing:
try server.use("logging", libxev_http.loggingMiddleware);Generates unique request IDs:
try server.use("request-id", libxev_http.requestIdMiddleware);Adds CORS headers:
try server.use("cors", libxev_http.corsMiddleware);Adds security-related headers:
try server.use("security", libxev_http.securityHeadersMiddleware);Provides basic HTTP authentication:
try route.use("auth", libxev_http.basicAuthMiddleware);Validates JSON request bodies:
try server.use("json-parser", libxev_http.jsonBodyParserMiddleware);Adds rate limiting headers:
try route.use("rate-limit", libxev_http.rateLimitMiddleware);Handles errors gracefully:
try server.use("error-handler", libxev_http.errorHandlerMiddleware);Adds compression support:
try server.use("compression", libxev_http.compressionMiddleware);fn customMiddleware(ctx: *libxev_http.Context, next: libxev_http.NextFn) !void {
// Pre-processing: runs before the handler
std.log.info("Custom middleware executing", .{});
// Set some state
try ctx.setState("custom_processed", "true");
// Add a custom header
try ctx.setHeader("X-Custom-Middleware", "executed");
// Call the next middleware/handler
try next(ctx);
// Post-processing: runs after the handler (if needed)
std.log.info("Custom middleware completed", .{});
}fn validationMiddleware(ctx: *libxev_http.Context, next: libxev_http.NextFn) !void {
const content_type = ctx.getHeader("Content-Type");
if (content_type == null) {
ctx.status(.bad_request);
try ctx.json("{\"error\":\"Content-Type header required\"}");
return; // Don't call next() to stop the chain
}
// Validation passed, continue
return next(ctx);
}fn errorHandlingMiddleware(ctx: *libxev_http.Context, next: libxev_http.NextFn) !void {
next(ctx) catch |err| {
std.log.err("Middleware error: {}", .{err});
// Set error response
ctx.status(.internal_server_error);
try ctx.json("{\"error\":\"Internal server error\"}");
return err;
};
}const std = @import("std");
const libxev_http = @import("libxev-http");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var server = try libxev_http.createServer(allocator, "127.0.0.1", 8080);
defer server.deinit();
// Global middleware
try server.use("logging", libxev_http.loggingMiddleware);
try server.use("request-id", libxev_http.requestIdMiddleware);
try server.use("cors", libxev_http.corsMiddleware);
// Basic route
_ = try server.get("/", indexHandler);
// Protected route with custom middleware
const protected_route = try server.get("/api/protected", protectedHandler);
try protected_route.use("auth", libxev_http.basicAuthMiddleware);
try protected_route.use("custom", customMiddleware);
try server.listen();
}
fn indexHandler(ctx: *libxev_http.Context) !void {
try ctx.json("{\"message\":\"Hello World!\"}");
}
fn protectedHandler(ctx: *libxev_http.Context) !void {
const request_id = ctx.getState("request_id") orelse "unknown";
const response = try std.fmt.allocPrint(ctx.allocator,
"{{\"message\":\"Protected resource\",\"request_id\":\"{s}\"}}",
.{request_id});
defer ctx.allocator.free(response);
try ctx.json(response);
}
fn customMiddleware(ctx: *libxev_http.Context, next: libxev_http.NextFn) !void {
try ctx.setHeader("X-Custom", "middleware-executed");
return next(ctx);
}- Order matters: Register middleware in the order you want them to execute
- Always call next(): Unless you want to stop the chain (e.g., for authentication failures)
- Handle errors gracefully: Use error handling middleware to catch and respond to errors
- Use state for data sharing: Use
ctx.setState()andctx.getState()to share data between middleware - Keep middleware focused: Each middleware should have a single responsibility
- Test middleware independently: Write unit tests for your custom middleware
You can test the middleware system using the provided examples:
# Run the simple middleware test
zig build run-simple-middleware
# Run the comprehensive middleware example
zig build run-middleware
# Run middleware tests
zig build test-middlewareMiddleware can store and retrieve state using the context:
// Set state in middleware
try ctx.setState("user_id", "12345");
// Get state in handler or other middleware
const user_id = ctx.getState("user_id");You can create middleware that only runs under certain conditions:
fn conditionalMiddleware(ctx: *libxev_http.Context, next: libxev_http.NextFn) !void {
if (std.mem.eql(u8, ctx.getMethod(), "POST")) {
// Only run for POST requests
try ctx.setHeader("X-POST-Request", "true");
}
return next(ctx);
}You can compose multiple middleware functions:
fn composedMiddleware(ctx: *libxev_http.Context, next: libxev_http.NextFn) !void {
// Run multiple middleware in sequence
try authMiddleware(ctx, struct {
fn call(context: *libxev_http.Context) !void {
return validationMiddleware(context, next);
}
}.call);
}