v0.21

middleware

introduction

middleware provides a convenient mechanism for inspecting and filtering http requests entering your application. for example an authentication middleware might verify the user is logged in. if they are not the middleware redirects them to the login screen. if they are authenticated the middleware allows the request to proceed deeper into the application.

defining middleware

all custom middleware lives in the middlewares directory. you define middleware as a class with async methods. each method acts as an independent middleware handler.

1// middlewares/AuthMiddleware.js
2export default class AuthMiddleware {
3 async requireAuth(req, next) {
4 if (!Auth.check()) {
5 return redirect('/login', true);
6 }
7 return next();
8 }
9
10 async requireGuest(req, next) {
11 if (Auth.check()) {
12 return redirect('/home', true);
13 }
14 return next();
15 }
16}

you can generate a middleware stub using the cli.

1dstrn make:middleware AuthMiddleware

the next function

every middleware method receives the req object and a next closure. you must call next() to pass control to the next middleware in the execution chain. if you do not call next() the request halts at that middleware and the framework assumes you have handled the response.

some middleware may need access to the response object. you can declare res as the second argument and next as the third.

1export default class CustomMiddleware {
2 async intercept(req, res, next) {
3 res.set('X-Custom-Header', 'dstrn');
4 return next();
5 }
6}

halting the chain

to halt the request and prevent it from reaching the controller simply return a response helper like redirect(), abort(), or json() instead of calling next().

1// middlewares/AdminMiddleware.js
2export default class AdminMiddleware {
3 async requireAdmin(req, next) {
4 if (!Auth.check()) return redirect('/login');
5 if (Auth.user().power < 5) return abort(403, 'unauthorized access');
6
7 return next();
8 }
9}

the middleware runner strictly monitors the response state. if a middleware sends headers or ends the writable stream the chain is immediately broken even if next() was accidentally called.

registering middleware

middleware is not registered globally. you attach it directly to your routes using the string syntax ClassName@method. the router resolves these strings automatically.

you can apply middleware to a single route.

1Route.middleware('AuthMiddleware@requireAuth').get('/profile', 'app.UserProfileController@show');

you can apply middleware to a group of routes.

1Route.group({ middleware: ['AuthMiddleware@requireAuth'] }, (auth) => {
2 auth.get('/dashboard', 'app.IndexController@dashboard');
3 auth.get('/library', 'app.IndexController@library');
4});

you can chain multiple middleware by providing an array.

1Route.group({ middleware: ['AuthMiddleware@requireAuth', 'AdminMiddleware@requireAdmin'] }, (admin) => {
2 admin.get('/users', 'admin.AdminController@list');
3});

you can also define routes using an inline array where all items except the last are treated as middleware.

1Route.post('/device/approve', ['AuthMiddleware@requireAuth', 'auth.AuthController@approvePost']);

middleware parameters

dframework does not support passing parameters directly through the middleware string syntax. if your middleware requires dynamic configuration you should define separate methods on the class or pass data via the req object.

1export default class DeviceMiddleware {
2 async verifyDevice(req, next) {
3 const devices = await Device.where({ user_id: Auth.user().id, revoked: false }).get();
4
5 // attach data to the request for the controller
6 req.devices = devices;
7
8 return next();
9 }
10}

hot reloading

in the local environment the framework watches the middlewares directory. when you modify a middleware file the runner automatically invalidates its cache and reloads the class. this means your middleware logic hot reloads instantly without requiring a server restart.

rate limiting

the built in RateLimiter uses a sliding window algorithm. it tracks request counts per key within a configurable time window, automatically cleans up expired entries, and sets standard rate limit response headers.

1import { RateLimiter } from 'dframework';
2
3const limiter = new RateLimiter({
4 windowMs: 60 * 1000, // 1 minute
5 max: 60, // 60 requests per window
6 message: 'too many requests, please slow down',
7});
8
9Route.middleware(limiter.middleware()).group({ prefix: '/api' }, (Route) => {
10 Route.post('/messages', 'MessageController@store');
11 Route.post('/uploads', 'UploadController@store');
12});

when the limit is exceeded, the response includes Retry-After, X-RateLimit-Limit, and X-RateLimit-Remaining headers alongside a 429 status.

option default description
windowMs 60000 time window in milliseconds
max 60 maximum requests per window
message 'too many requests' message returned on limit exceeded
keyGenerator client ip (req) => string, custom grouping key
trustedProxies [] trusted proxy ips for x-forwarded-for resolution

a custom keyGenerator is useful for per user limiting:

1const limiter = new RateLimiter({
2 windowMs: 60 * 1000,
3 max: 20,
4 keyGenerator: (req) => Auth.user()?.id || req._req.socket.remoteAddress,
5});