Skip to main content

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:

  1. Logger
  2. Auth
  3. RateLimit
  4. 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