Middleware#
Middleware functions are the building blocks of dart_express applications. They process requests, modify responses, and control the flow of execution.
What is Middleware?#
Middleware is a function that has access to the request object (req), response object (res), and the next middleware function (next).
Future<void> myMiddleware(Request req, Response res, NextFunction next) async {
// Do something before the route handler
print('Request received');
// Pass control to the next middleware
await next();
// Do something after the route handler
print('Response sent');
}
Using Middleware#
Global Middleware#
Applies to all routes:
final app = DartExpress();
// Logging middleware
app.use((req, res, next) async {
print('[${DateTime.now()}] ${req.method} ${req.uri.path}');
await next();
});
// Your routes
app.get('/', (req, res) => res.text('Hello!'));
Route-Specific Middleware#
Apply to specific routes:
Future<void> authMiddleware(Request req, Response res, NextFunction next) async {
final token = req.headers['authorization'];
if (token == null) {
return res.status(401).json({'error': 'Unauthorized'});
}
// Verify token...
await next();
}
app.get('/protected', authMiddleware, (req, res) {
res.json({'message': 'Secret data'});
});
Built-in Middleware#
CORS#
Enable Cross-Origin Resource Sharing:
app.use(app.cors(
allowedOrigins: ['https://example.com'],
allowedMethods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
));
Rate Limiting#
Protect against abuse:
app.use(app.rateLimit(
maxRequests: 100,
windowMs: 60000, // 1 minute
));
Middleware Patterns#
Authentication#
Future<void> requireAuth(Request req, Response res, NextFunction next) async {
if (!req.session.containsKey('userId')) {
return res.status(401).json({'error': 'Please log in'});
}
await next();
}
app.get('/profile', requireAuth, (req, res) {
final userId = req.session['userId'];
res.json({'userId': userId});
});
Request Validation#
Future<void> validateUser(Request req, Response res, NextFunction next) async {
final body = await req.body;
if (body['email'] == null || body['password'] == null) {
return res.status(400).json({
'error': 'Email and password required'
});
}
await next();
}
app.post('/signup', validateUser, (req, res) async {
final body = await req.body;
// Create user...
});
Error Handling#
app.use((req, res, next) async {
try {
await next();
} catch (e, stack) {
print('Error: $e');
print(stack);
res.status(500).json({
'error': 'Internal server error',
'message': e.toString(),
});
}
});
Request Timing#
app.use((req, res, next) async {
final start = DateTime.now();
await next();
final duration = DateTime.now().difference(start);
print('${req.method} ${req.uri.path} - ${duration.inMilliseconds}ms');
});
Middleware Order#
Middleware executes in the order it's defined:
// 1. Logging
app.use((req, res, next) async {
print('1: Before');
await next();
print('1: After');
});
// 2. Auth check
app.use((req, res, next) async {
print('2: Before');
await next();
print('2: After');
});
// 3. Route handler
app.get('/', (req, res) {
print('3: Handler');
res.text('Hello!');
});
Output:
1: Before
2: Before
3: Handler
2: After
1: After
Stopping the Chain#
Don't call next() to stop:
app.use((req, res, next) async {
if (req.headers['api-key'] != 'secret') {
return res.status(403).json({'error': 'Forbidden'});
// next() NOT called - stops here
}
await next();
});
Modifying Request/Response#
Middleware can modify the request/response objects:
app.use((req, res, next) async {
// Add custom data to request
req.session['timestamp'] = DateTime.now().toIso8601String();
// Add custom headers to response
res.setHeader('X-Powered-By', 'dart_express');
await next();
});
Third-Party Middleware#
Create reusable middleware packages:
// my_logger_middleware.dart
Future<void> loggerMiddleware({
bool showTimestamp = true,
}) {
return (Request req, Response res, NextFunction next) async {
final time = showTimestamp ? '[${DateTime.now()}] ' : '';
print('$time${req.method} ${req.uri.path}');
await next();
};
}
// Usage
app.use(loggerMiddleware(showTimestamp: true));
Best Practices#
- Keep middleware focused - Each middleware should do one thing well
- Always call next() - Unless you're terminating the request
- Error handling - Wrap route handlers in try-catch middleware
- Order matters - Put logging first, error handling last
- Async/await - Always use async and await next()