Middleware
Middleware functions execute before your route handlers. They're perfect for authentication, logging, request modification, and more.
Creating Middleware
Middleware functions receive Request and optionally Response:
import { Request, Response } from 'muzu';
function LoggerMiddleware(req: Request) {
console.log(`${req.method} ${req.url}`);
}
function TimestampMiddleware(req: Request) {
req.timestamp = Date.now();
}
Applying Middleware
Use the @Middleware decorator to apply middleware to routes:
import { Controller, Get, Middleware } from 'muzu';
function AuthMiddleware(req: Request) {
const token = req.headers.authorization;
if (!token) {
throw new HttpException('Unauthorized', 401);
}
// Attach user to request
req.user = verifyToken(token);
}
@Controller('/api')
class ApiController {
@Get('/public')
getPublic() {
return { message: 'Public endpoint' };
}
@Get('/protected')
@Middleware(AuthMiddleware)
getProtected(req: Request) {
return {
message: 'Protected endpoint',
user: req.user
};
}
}
Multiple Middleware
Apply multiple middleware functions in order:
function Logger(req: Request) {
console.log(`Request: ${req.method} ${req.url}`);
}
function Auth(req: Request) {
req.user = authenticate(req.headers.authorization);
}
function RateLimit(req: Request) {
if (isRateLimited(req.user.id)) {
throw new HttpException('Too many requests', 429);
}
}
@Controller('/api')
class ApiController {
@Get('/data')
@Middleware(Logger, Auth, RateLimit)
getData(req: Request) {
return { data: [], user: req.user };
}
}
Middleware executes in the order specified:
- Logger
- Auth
- RateLimit
- Route handler
Async Middleware
Middleware can be async:
async function DatabaseMiddleware(req: Request) {
req.db = await connectToDatabase();
}
async function UserMiddleware(req: Request) {
const userId = req.headers['x-user-id'];
req.user = await database.users.findById(userId);
}
@Controller('/api')
class ApiController {
@Get('/profile')
@Middleware(DatabaseMiddleware, UserMiddleware)
async getProfile(req: Request) {
return {
user: req.user,
profile: await req.db.profiles.find(req.user.id)
};
}
}
Common Middleware Examples
Authentication Middleware
function AuthMiddleware(req: Request) {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
throw new HttpException('No token provided', 401);
}
try {
const decoded = jwt.verify(token, SECRET_KEY);
req.user = decoded;
} catch (error) {
throw new HttpException('Invalid token', 401);
}
}
Logging Middleware
function LoggingMiddleware(req: Request) {
const start = Date.now();
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
// Store start time for response logging
req.startTime = start;
}
CORS Middleware
function CorsMiddleware(req: Request, res: Response) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (req.method === 'OPTIONS') {
res.statusCode = 204;
res.end();
}
}
Request ID Middleware
import { v4 as uuid } from 'uuid';
function RequestIdMiddleware(req: Request, res: Response) {
const requestId = uuid();
req.requestId = requestId;
res.setHeader('X-Request-ID', requestId);
}
Rate Limiting Middleware
const rateLimits = new Map();
function RateLimitMiddleware(req: Request) {
const clientId = req.headers['x-client-id'] || req.socket.remoteAddress;
const now = Date.now();
const windowMs = 60 * 1000; // 1 minute
const maxRequests = 100;
if (!rateLimits.has(clientId)) {
rateLimits.set(clientId, { count: 1, resetTime: now + windowMs });
return;
}
const limit = rateLimits.get(clientId);
if (now > limit.resetTime) {
limit.count = 1;
limit.resetTime = now + windowMs;
} else {
limit.count++;
if (limit.count > maxRequests) {
throw new HttpException('Rate limit exceeded', 429);
}
}
}
Modifying Request
Middleware can add properties to the request:
function EnrichRequestMiddleware(req: Request) {
// Add timestamp
req.timestamp = Date.now();
// Add request ID
req.requestId = generateId();
// Add user agent info
req.userAgent = parseUserAgent(req.headers['user-agent']);
// Add custom data
req.custom = {
ip: req.socket.remoteAddress,
protocol: req.protocol
};
}
@Controller('/api')
class ApiController {
@Get('/info')
@Middleware(EnrichRequestMiddleware)
getInfo(req: Request) {
return {
timestamp: req.timestamp,
requestId: req.requestId,
userAgent: req.userAgent,
custom: req.custom
};
}
}
Error Handling in Middleware
Throw exceptions to stop execution:
function ValidateApiKeyMiddleware(req: Request) {
const apiKey = req.headers['x-api-key'];
if (!apiKey) {
throw new HttpException('API key required', 401);
}
if (!isValidApiKey(apiKey)) {
throw new HttpException('Invalid API key', 403);
}
// Continue to next middleware/handler
}
Middleware Performance
Muzu optimizes middleware at build time:
Middleware Composition
Multiple middleware are composed into a single function:
// Your code
@Middleware(Logger, Auth, RateLimit)
// Compiled to single function (no array iteration)
async function composedMiddleware(req, res) {
await Logger(req, res);
await Auth(req, res);
await RateLimit(req, res);
}
Single Middleware
Single middleware is used directly (no composition overhead):
// Your code
@Middleware(AuthMiddleware)
// Used directly (no wrapping)
AuthMiddleware(req, res);
Middleware Best Practices
Keep Middleware Focused
// ✅ Good - Single responsibility
function AuthMiddleware(req: Request) {
req.user = authenticate(req);
}
function LoggingMiddleware(req: Request) {
console.log(req.method, req.url);
}
// ❌ Bad - Multiple responsibilities
function MegaMiddleware(req: Request) {
req.user = authenticate(req);
console.log(req.method, req.url);
req.timestamp = Date.now();
checkRateLimit(req);
}
Handle Errors Properly
// ✅ Good - Throw exceptions
function AuthMiddleware(req: Request) {
if (!req.headers.authorization) {
throw new HttpException('Unauthorized', 401);
}
}
// ❌ Bad - Silent failures
function AuthMiddleware(req: Request, res: Response) {
if (!req.headers.authorization) {
res.statusCode = 401;
res.end('Unauthorized');
}
}
Use Type Guards
// ✅ Good - Type-safe
interface AuthRequest extends Request {
user: User;
}
function requireAuth(req: Request): req is AuthRequest {
if (!req.user) {
throw new HttpException('Unauthorized', 401);
}
return true;
}
@Get('/profile')
@Middleware(AuthMiddleware)
getProfile(req: Request) {
if (requireAuth(req)) {
return { user: req.user }; // TypeScript knows req.user exists
}
}
Avoid Heavy Computation
// ✅ Good - Lightweight
function QuickCheckMiddleware(req: Request) {
if (cache.has(req.url)) {
req.cached = true;
}
}
// ❌ Bad - Heavy computation
function HeavyMiddleware(req: Request) {
req.analysis = performComplexAnalysis(req.body);
req.recommendations = generateRecommendations(req.analysis);
}
Global vs Route Middleware
Muzu currently supports route-level middleware only:
// Route-level middleware
@Get('/protected')
@Middleware(AuthMiddleware)
getProtected() { /* ... */ }
// Apply to multiple routes
@Get('/admin/users')
@Middleware(AuthMiddleware, AdminMiddleware)
getUsers() { /* ... */ }
@Get('/admin/settings')
@Middleware(AuthMiddleware, AdminMiddleware)
getSettings() { /* ... */ }
Future Feature
Global middleware (applied to all routes) is planned for a future release.
Next Steps
- Learn about Request Validation to validate request data
- Explore Exception Handling for error management
- See Swagger Documentation for API docs