Skip to main content

Middleware

Middleware in Balda are functions that have access to the request object, response object, and the next middleware function in the application's request-response cycle. They can execute code, modify the request and response objects, end the request-response cycle, and call the next middleware function.

Basic Middleware

Function Signature

type Middleware = (
req: Request,
res: Response,
next: NextFunction
) => void | Promise<void>;

Simple Middleware Example

const logger = (req, res, next) => {
console.log(`${req.method} ${req.url} - ${new Date().toISOString()}`);
next();
};

const auth = (req, res, next) => {
const token = req.headers.authorization;

if (!token) {
return res.unauthorized({ error: 'Authentication required' });
}

// Verify token logic here
req.user = { id: 1, name: 'John' };
next();
};

Middleware Application

Global Middleware

const server = new Server({ port: 3000 });

// Single middleware
server.use(loggerMiddleware);

// Multiple middleware
server.use(authMiddleware, loggingMiddleware);

Controller-Level Middleware

@controller('/users')
@middleware(authMiddleware)
export class UsersController {
@get('/') getAll(req, res) { /* uses authMiddleware */ }

@get('/public')
@middleware(publicMiddleware) // Override
getPublic(req, res) { /* uses publicMiddleware instead */ }
}

Route-Level Middleware

// Direct registration
server.get('/admin', authMiddleware, (req, res) => res.json({ ok: true }));

// Controller route
@get('/admin', { middleware: [authMiddleware, adminMiddleware] })
getAdmin(req, res) { res.json({ users: [] }); }

Common Middleware Patterns

Authentication

const authMiddleware = async (req, res, next) => {
const token = req.headers.authorization?.replace('Bearer ', '');

if (!token) {
return res.unauthorized({ error: 'Token required' });
}

try {
req.user = jwt.verify(token, process.env.JWT_SECRET);
next();
} catch {
return res.unauthorized({ error: 'Invalid token' });
}
};

Role-Based Authorization

const requireRole = (role) => (req, res, next) => {
if (!req.user || req.user.role !== role) {
return res.forbidden({ error: 'Insufficient permissions' });
}
next();
};

// Usage
@get('/admin', { middleware: [authMiddleware, requireRole('admin')] })
getAdmin(req, res) { res.json({ admin: true }); }

Request Logging

const logger = (req, res, next) => {
const start = Date.now();
res.on('finish', () => {
console.log(`${req.method} ${req.url} ${res.statusCode} - ${Date.now() - start}ms`);
});
next();
};
tip

For production use, leverage built-in plugins:

  • cors plugin for CORS handling
  • rateLimiter plugin for rate limiting
  • log plugin for request/response logging

See Plugins Overview for details.

Middleware Order

Middleware executes in order: Global → Controller → Route → Handler

server.use(logger);                          // 1. Global
server.use(cors); // 2. Global

@controller('/users')
@middleware(authMiddleware) // 3. Controller
export class UsersController {
@get('/admin', { middleware: [adminMiddleware] }) // 4. Route
getAdmin(req, res) { // 5. Handler
// Execution: logger → cors → authMiddleware → adminMiddleware → handler
}
}

Async Middleware

const asyncAuth = async (req, res, next) => {
try {
const user = await verifyToken(req.headers.authorization);
req.user = user;
next();
} catch {
return res.unauthorized({ error: 'Invalid token' });
}
};

Conditional Middleware

const conditionalAuth = (req, res, next) => {
if (req.path.startsWith('/public')) {
return next(); // Skip auth for public routes
}

const token = req.headers.authorization;
if (!token) {
return res.unauthorized({ error: 'Authentication required' });
}

req.user = { id: 1 };
next();
};