Middleware
Middleware functions have access to the request context and can control the request flow. They’re perfect for cross-cutting concerns like logging, authentication, and error handling.
Basic Middleware
Section titled “Basic Middleware”Middleware receives the context and a next function:
import { Middleware } from 'shokupan';
const logger: Middleware = async (ctx, next) => { console.log(`${ctx.method} ${ctx.path}`); const start = Date.now();
const result = await next();
console.log(`${ctx.method} ${ctx.path} - ${Date.now() - start}ms`); return result;};
app.use(logger);Global Middleware
Section titled “Global Middleware”Apply middleware to all routes:
// Loggingapp.use(async (ctx, next) => { console.log(`→ ${ctx.method} ${ctx.path}`); return next();});
// Request IDapp.use(async (ctx, next) => { ctx.state.requestId = crypto.randomUUID(); return next();});
// Add routes after middlewareapp.get('/', (ctx) => { return { requestId: ctx.state.requestId };});Route-Specific Middleware
Section titled “Route-Specific Middleware”Apply middleware to specific routes:
const authenticate = async (ctx, next) => { const token = ctx.headers.get('authorization');
if (!token) { return ctx.json({ error: 'Unauthorized' }, 401); }
// Validate token and attach user ctx.state.user = { id: '123', name: 'Alice' };
return next();};
// Apply to single routeapp.get('/protected', authenticate, (ctx) => { return { user: ctx.state.user };});
// Apply to multiple handlersapp.post('/admin', authenticate, checkAdmin, (ctx) => { return { admin: true }; });Router Middleware
Section titled “Router Middleware”Apply middleware to all routes in a router:
import { ShokupanRouter } from 'shokupan';
const apiRouter = new ShokupanRouter();
// Middleware for all routes in this routerapiRouter.use(authenticate);
apiRouter.get('/users', (ctx) => ({ users: [] }));apiRouter.get('/posts', (ctx) => ({ posts: [] }));
app.mount('/api', apiRouter);Controller Middleware
Section titled “Controller Middleware”Use the @Use decorator for controllers:
import { Use } from 'shokupan';
// On entire controller@Use(authenticate)export class AdminController { @Get('/dashboard') getDashboard() { }}
// On specific methodsexport class UserController { @Get('/') @Use(rateLimit) getUsers() { }}Error Handling
Section titled “Error Handling”Middleware can catch and handle errors:
const errorHandler: Middleware = async (ctx, next) => { try { return await next(); } catch (error) { console.error('Error:', error);
return ctx.json({ error: 'Internal Server Error', message: error.message }, 500); }};
// Add early in middleware chainapp.use(errorHandler);Request Timing
Section titled “Request Timing”Track request duration:
const timing: Middleware = async (ctx, next) => { const start = Date.now(); const result = await next(); const duration = Date.now() - start;
// Add timing header ctx.set('X-Response-Time', `${duration}ms`);
return result;};
app.use(timing);Authentication
Section titled “Authentication”Create an authentication middleware:
const authenticate: Middleware = async (ctx, next) => { const token = ctx.headers.get('authorization')?.replace('Bearer ', '');
if (!token) { return ctx.json({ error: 'No token provided' }, 401); }
try { // Verify JWT token const user = await verifyToken(token); ctx.state.user = user; return next(); } catch (error) { return ctx.json({ error: 'Invalid token' }, 401); }};Request ID
Section titled “Request ID”Add unique request IDs:
const requestId: Middleware = async (ctx, next) => { const id = crypto.randomUUID(); ctx.state.requestId = id; ctx.set('X-Request-ID', id); return next();};
app.use(requestId);
app.get('/', (ctx) => { console.log(`Request ID: ${ctx.state.requestId}`); return { requestId: ctx.state.requestId };});CORS Headers
Section titled “CORS Headers”Simple CORS middleware (use CORS plugin for production):
const cors: Middleware = async (ctx, next) => { ctx.set('Access-Control-Allow-Origin', '*'); ctx.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE'); ctx.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (ctx.method === 'OPTIONS') { return ctx.status(204); }
return next();};
app.use(cors);Conditional Middleware
Section titled “Conditional Middleware”Apply middleware conditionally:
const conditionalAuth: Middleware = async (ctx, next) => { // Skip auth for public endpoints if (ctx.path.startsWith('/public')) { return next(); }
// Require auth for all other endpoints return authenticate(ctx, next);};
app.use(conditionalAuth);Middleware Order
Section titled “Middleware Order”Middleware executes in the order it’s added:
// ✅ Correct orderapp.use(errorHandler); // 1. Error handling firstapp.use(logger); // 2. Loggingapp.use(authenticate); // 3. Authenticationapp.use(rateLimit); // 4. Rate limiting
// Routesapp.get('/', handler);
// Request flow:// errorHandler → logger → authenticate → rateLimit → handler// Response flow (reversed):// handler → rateLimit → authenticate → logger → errorHandlerModifying Responses
Section titled “Modifying Responses”Middleware can modify responses:
const addHeaders: Middleware = async (ctx, next) => { const result = await next();
// Add custom headers to response ctx.set('X-Powered-By', 'Shokupan'); ctx.set('X-Version', '1.0.0');
return result;};
app.use(addHeaders);State Sharing
Section titled “State Sharing”Use ctx.state to share data between middleware:
const loadUser: Middleware = async (ctx, next) => { const userId = ctx.query.get('userId'); if (userId) { ctx.state.user = await fetchUser(userId); } return next();};
const checkPermissions: Middleware = async (ctx, next) => { if (!ctx.state.user?.isAdmin) { return ctx.json({ error: 'Forbidden' }, 403); } return next();};
app.get('/admin', loadUser, checkPermissions, (ctx) => { return { user: ctx.state.user };});Built-in Plugins
Section titled “Built-in Plugins”Shokupan provides many built-in middleware plugins:
import { Cors, Compression, RateLimit, SecurityHeaders} from 'shokupan';
app.use(Cors());app.use(Compression());app.use(RateLimit({ windowMs: 15 * 60 * 1000, max: 100 }));app.use(SecurityHeaders());See the Plugins section for more details.
Next Steps
Section titled “Next Steps”- Context API - Full context reference
- CORS Plugin - Configure CORS
- Rate Limiting - Prevent abuse
- Authentication - OAuth2 support