Routing and Middleware in Express
Advanced routing and middleware patterns are essential for building scalable Express applications. This article covers advanced techniques.
Introduction
Advanced routing and middleware provide:
- Organized code structure
- Reusable middleware
- Complex routing patterns
- Request processing pipelines
- Clean architecture
Understanding advanced patterns helps you:
- Build maintainable applications
- Implement authentication
- Handle cross-cutting concerns
- Create flexible APIs
- Scale applications
Advanced Routing
Route Parameters and Patterns
const express = require('express');
const app = express();
// โ
Good: Route parameters
app.get('/users/:id', (req, res) => {
const userId = req.params.id;
res.json({ id: userId });
});
// โ
Good: Multiple parameters
app.get('/users/:userId/posts/:postId', (req, res) => {
const { userId, postId } = req.params;
res.json({ userId, postId });
});
// โ
Good: Optional parameters
app.get('/files/:name?', (req, res) => {
const fileName = req.params.name || 'default';
res.json({ file: fileName });
});
// โ
Good: Regex patterns
app.get('/users/:id(\\d+)', (req, res) => {
const userId = req.params.id;
res.json({ id: userId });
});
// โ
Good: Query parameters
app.get('/search', (req, res) => {
const { q, page = 1, limit = 10 } = req.query;
res.json({ query: q, page, limit });
});
// โ
Good: Wildcard routes
app.get('/api/*', (req, res) => {
res.json({ path: req.params[0] });
});
// โ
Good: Route chaining
app.route('/users/:id')
.get((req, res) => {
res.json({ id: req.params.id });
})
.put((req, res) => {
res.json({ updated: req.params.id });
})
.delete((req, res) => {
res.json({ deleted: req.params.id });
});
Router Objects
// routes/users.js
const express = require('express');
const router = express.Router();
// โ
Good: Router middleware
router.use((req, res, next) => {
console.log('User route accessed');
next();
});
// โ
Good: Router parameters
router.param('id', (req, res, next, id) => {
console.log('User ID:', id);
req.userId = id;
next();
});
// โ
Good: Router routes
router.get('/', (req, res) => {
res.json([{ id: 1, name: 'John' }]);
});
router.get('/:id', (req, res) => {
res.json({ id: req.userId, name: 'John' });
});
router.post('/', (req, res) => {
res.status(201).json(req.body);
});
module.exports = router;
// routes/posts.js
const express = require('express');
const router = express.Router({ mergeParams: true });
router.get('/', (req, res) => {
const userId = req.params.userId;
res.json([{ id: 1, userId, title: 'Post 1' }]);
});
module.exports = router;
// app.js
const express = require('express');
const userRoutes = require('./routes/users');
const postRoutes = require('./routes/posts');
const app = express();
app.use('/users', userRoutes);
app.use('/users/:userId/posts', postRoutes);
app.listen(3000);
Middleware Patterns
Middleware Chains
const express = require('express');
const app = express();
// โ
Good: Middleware chain
const authenticate = (req, res, next) => {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: 'Unauthorized' });
}
req.user = { id: 1, name: 'John' };
next();
};
const authorize = (role) => {
return (req, res, next) => {
if (req.user.role !== role) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
};
const validate = (schema) => {
return (req, res, next) => {
const { error } = schema.validate(req.body);
if (error) {
return res.status(400).json({ error: error.message });
}
next();
};
};
// โ
Good: Apply middleware chain
app.post(
'/admin/users',
authenticate,
authorize('admin'),
validate(userSchema),
(req, res) => {
res.json({ message: 'User created' });
}
);
// โ
Good: Conditional middleware
app.get('/data', (req, res, next) => {
if (req.query.admin) {
authenticate(req, res, next);
} else {
next();
}
}, (req, res) => {
res.json({ data: 'public data' });
});
Custom Middleware
// middleware/logger.js
function logger(req, res, next) {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.url} - ${res.statusCode} (${duration}ms)`);
});
next();
}
module.exports = logger;
// middleware/requestId.js
const { v4: uuidv4 } = require('uuid');
function requestId(req, res, next) {
req.id = uuidv4();
res.setHeader('X-Request-ID', req.id);
next();
}
module.exports = requestId;
// middleware/cors.js
function cors(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
}
module.exports = cors;
// middleware/errorHandler.js
function errorHandler(err, req, res, next) {
console.error('Error:', err);
const status = err.status || 500;
const message = err.message || 'Internal server error';
res.status(status).json({
error: {
status,
message,
requestId: req.id
}
});
}
module.exports = errorHandler;
// app.js
const express = require('express');
const logger = require('./middleware/logger');
const requestId = require('./middleware/requestId');
const cors = require('./middleware/cors');
const errorHandler = require('./middleware/errorHandler');
const app = express();
app.use(logger);
app.use(requestId);
app.use(cors);
app.use(express.json());
app.get('/', (req, res) => {
res.json({ message: 'Hello' });
});
app.use(errorHandler);
app.listen(3000);
Authentication and Authorization
JWT Authentication
// middleware/auth.js
const jwt = require('jsonwebtoken');
const authenticate = (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (err) {
res.status(401).json({ error: 'Invalid token' });
}
};
const authorize = (roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
};
module.exports = { authenticate, authorize };
// routes/auth.js
const express = require('express');
const jwt = require('jsonwebtoken');
const router = express.Router();
router.post('/login', (req, res) => {
const { email, password } = req.body;
// Verify credentials
const user = { id: 1, email, role: 'user' };
const token = jwt.sign(user, process.env.JWT_SECRET, {
expiresIn: '1h'
});
res.json({ token });
});
module.exports = router;
// routes/protected.js
const express = require('express');
const { authenticate, authorize } = require('../middleware/auth');
const router = express.Router();
router.get('/profile', authenticate, (req, res) => {
res.json({ user: req.user });
});
router.get('/admin', authenticate, authorize(['admin']), (req, res) => {
res.json({ message: 'Admin panel' });
});
module.exports = router;
Request Validation
Input Validation Middleware
// middleware/validate.js
const { body, validationResult } = require('express-validator');
const validate = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
next();
};
module.exports = validate;
// routes/users.js
const express = require('express');
const { body } = require('express-validator');
const validate = require('../middleware/validate');
const router = express.Router();
router.post(
'/',
[
body('email').isEmail().normalizeEmail(),
body('password').isLength({ min: 8 }),
body('name').trim().notEmpty()
],
validate,
(req, res) => {
res.status(201).json(req.body);
}
);
router.put(
'/:id',
[
body('email').optional().isEmail(),
body('password').optional().isLength({ min: 8 })
],
validate,
(req, res) => {
res.json({ id: req.params.id, ...req.body });
}
);
module.exports = router;
Error Handling Middleware
Comprehensive Error Handling
// middleware/errorHandler.js
class AppError extends Error {
constructor(message, status) {
super(message);
this.status = status;
}
}
const errorHandler = (err, req, res, next) => {
console.error('Error:', err);
const status = err.status || 500;
const message = err.message || 'Internal server error';
res.status(status).json({
error: {
status,
message,
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
}
});
};
const asyncHandler = (fn) => {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
};
module.exports = { AppError, errorHandler, asyncHandler };
// routes/users.js
const express = require('express');
const { AppError, asyncHandler } = require('../middleware/errorHandler');
const router = express.Router();
router.get('/:id', asyncHandler(async (req, res) => {
const user = await getUser(req.params.id);
if (!user) {
throw new AppError('User not found', 404);
}
res.json(user);
}));
module.exports = router;
// app.js
const express = require('express');
const { errorHandler } = require('./middleware/errorHandler');
const app = express();
app.use(express.json());
// Routes
app.use('/users', require('./routes/users'));
// Error handling
app.use((req, res) => {
res.status(404).json({ error: 'Route not found' });
});
app.use(errorHandler);
app.listen(3000);
Best Practices
-
Order middleware correctly:
// โ Good: Correct order app.use(express.json()); app.use(authenticate); app.use(routes); app.use(errorHandler); // โ Bad: Wrong order app.use(errorHandler); app.use(routes); app.use(authenticate); -
Use async/await with error handling:
// โ Good: Async handler const asyncHandler = (fn) => (req, res, next) => { Promise.resolve(fn(req, res, next)).catch(next); }; // โ Bad: No error handling app.get('/', async (req, res) => { const data = await fetchData(); res.json(data); }); -
Separate concerns:
// โ Good: Separate middleware app.use(authenticate); app.use(authorize); app.use(validate); // โ Bad: Mixed concerns app.use((req, res, next) => { // Authentication, authorization, validation all here });
Summary
Advanced routing and middleware are essential. Key takeaways:
- Use route parameters and patterns
- Create reusable middleware
- Implement authentication and authorization
- Validate input properly
- Handle errors comprehensively
- Organize code by concern
- Use async/await with error handling
- Build scalable applications
Related Resources
Next Steps
- Learn about Database Integration
- Explore Building REST APIs
- Study Authentication
- Practice routing and middleware
- Build complex Express applications
Comments