Middleware System
Novaxjs2 provides a flexible middleware system for request processing, error handling, and response transformation. Middleware functions have access to the request and response objects and can execute any code, make changes to these objects, or end the request-response cycle.
Standard Middleware
Global middleware that runs for every request:
// Logging middleware
app.useMiddleware((req, res, next) => {
console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
next(); // Call next middleware
});
// Authentication middleware
app.useMiddleware((req, res, next) => {
const token = req.headers.authorization;
if (token === 'secret-token') {
req.user = { id: 1, name: 'John Doe' };
next();
} else {
res.status(401).end('Unauthorized');
}
});
// Body parsing middleware (built-in)
// JSON and form data parsing is automatic in Novaxjs2
Error Middleware
Middleware that handles errors in the application:
// Error handling middleware
app.useErrorMiddleware((err, req, res, next) => {
console.error('Error:', err.stack);
// Custom error responses based on error type
if (err.statusCode === 404) {
return res.status(404).end('Page not found');
}
res.status(500).end('Internal Server Error');
});
// Async error handling
app.useErrorMiddleware(async (err, req, res, next) => {
// Log error to database or external service
await logErrorToService(err, req);
res.status(500).json({
error: 'Something went wrong',
requestId: req.id // Custom request ID
});
});
Built-in Middleware
Novaxjs2 includes several built-in middleware functions:
// Static file serving
app.serveStatic('public'); // Serve files from 'public' directory
// Automatic body parsing for:
// - JSON (application/json)
// - Form data (application/x-www-form-urlencoded)
// - Multipart form data (multipart/form-data) for file uploads
// CORS support
app.cors({
origins: ['https://example.com', 'http://localhost:3000'],
methods: 'GET, POST, PUT, DELETE, OPTIONS',
headers: 'Content-Type, Authorization',
credentials: true
});
Middleware Execution Order
Middleware executes in the order they are defined. Understanding the execution flow is crucial:
app.useMiddleware((req, res, next) => {
console.log('Middleware 1');
next();
});
app.useMiddleware((req, res, next) => {
console.log('Middleware 2');
next();
});
app.get('/', (req, res) => {
console.log('Route handler');
return 'Hello World';
});
// Execution order:
// 1. Middleware 1
// 2. Middleware 2
// 3. Route handler
Middleware Best Practices
- Always call
next()
to pass control to the next middleware - Handle errors properly in error middleware
- Use async middleware for I/O operations
- Keep middleware focused on single responsibilities
Route-Specific Middleware
Novaxjs2 supports adding middleware to specific routes, allowing for fine-grained control over request processing.
Adding Middleware to Routes
Apply middleware to individual routes:
// Middleware function
function logRequest(req, res, next) {
console.log(`Request: ${req.method} ${req.url}`);
next();
}
function requireAuth(req, res, next) {
if (!req.headers.authorization) {
return res.status(401).end('Authentication required');
}
next();
}
// Route with middleware
app.get('/protected', logRequest, requireAuth, (req, res) => {
return 'This route is protected by middleware';
});
// POST route with middleware
app.post('/api/data', logRequest, requireAuth, (req, res) => {
return { message: 'Data processed successfully' };
});
Multiple Middleware
Chain multiple middleware functions together:
// Validation middleware
function validateUserData(req, res, next) {
const { username, email } = req.body;
if (!username || !email) {
return res.status(400).end('Username and email are required');
}
next();
}
// Sanitization middleware
function sanitizeData(req, res, next) {
if (req.body.username) {
req.body.username = req.body.username.trim();
}
next();
}
// Rate limiting middleware
function rateLimit(req, res, next) {
const ip = req.ip;
const requests = requestCount[ip] || 0;
if (requests > 100) {
return res.status(429).end('Too many requests');
}
requestCount[ip] = requests + 1;
next();
}
app.post('/register',
rateLimit,
validateUserData,
sanitizeData,
(req, res) => {
// Process registration
return { message: 'User registered successfully' };
}
);
Async Middleware
Middleware can be asynchronous for database operations or API calls:
// Async middleware for database operations
async function loadUser(req, res, next) {
try {
const user = await User.findById(req.params.userId);
if (!user) {
return res.status(404).end('User not found');
}
req.user = user;
next();
} catch (error) {
next(error);
}
}
// Async middleware for API calls
async function fetchExternalData(req, res, next) {
try {
const response = await fetch('https://api.example.com/data');
req.externalData = await response.json();
next();
} catch (error) {
next(error);
}
}
app.get('/user/:userId/profile',
loadUser,
fetchExternalData,
(req, res) => {
return {
user: req.user,
externalData: req.externalData
};
}
);
Error Handling in Route Middleware
Proper error handling in route-specific middleware:
// Error-prone middleware
function riskyOperation(req, res, next) {
try {
// Some operation that might fail
if (Math.random() > 0.5) {
throw new Error('Something went wrong');
}
next();
} catch (error) {
next(error); // Pass error to error middleware
}
}
// Alternatively, use async/await with try/catch
async function asyncRiskyOperation(req, res, next) {
try {
await someAsyncOperation();
next();
} catch (error) {
next(error);
}
}
app.get('/risky', riskyOperation, (req, res) => {
return 'This might fail sometimes';
});