Middleware Composition Guide
This guide explains how to use the middleware composition system to create reusable and composable middleware functions.
Overview
The middleware composition system provides:
- Token Verification: Base functions for verifying different types of tokens
- Permission Checking: Functions for checking user permissions
- Middleware Composition: Utilities for composing middleware functions
- Error Handling: Consistent error handling for middleware
Token Verification
The token verification system provides a base function for verifying different types of tokens:
| JavaScript |
|---|
| const { verifyAccessToken, verifyOTPToken } = require('../app/handlers/middlewares');
// Use in route definition
fastify.get('/protected-route', {
preHandler: verifyAccessToken
}, handler);
// Use in route definition with OTP token
fastify.post('/verify-otp', {
preHandler: verifyOTPToken
}, handler);
|
Available Token Verifiers
verifyAccessToken: Verifies standard access tokens
verifyOTPToken: Verifies one-time password tokens
verifyTempPassToken: Verifies temporary password tokens
verifyRefreshToken: Verifies refresh tokens
Permission Checking
The permission checking system provides functions for checking user permissions:
| JavaScript |
|---|
| const { checkPermission } = require('../app/handlers/middlewares');
const { PERMISSIONS } = require('../app/constants');
// Use in route definition
fastify.get('/admin/users', {
preHandler: checkPermission(PERMISSIONS.VIEW_USERS)
}, handler);
// Check multiple permissions (ANY)
fastify.put('/admin/users/:id', {
preHandler: checkPermission([
PERMISSIONS.UPDATE_USERS,
PERMISSIONS.MANAGE_USERS
])
}, handler);
// Check multiple permissions (ALL)
fastify.delete('/admin/users/:id', {
preHandler: checkPermission([
PERMISSIONS.DELETE_USERS,
PERMISSIONS.MANAGE_USERS
], { requireAll: true })
}, handler);
|
Middleware Composition
The middleware composition system provides utilities for composing middleware functions:
| JavaScript |
|---|
| const {
verifyAccessToken,
checkPermission,
utils: { compose }
} = require('../app/handlers/middlewares');
const { PERMISSIONS } = require('../app/constants');
// Compose multiple middleware functions
const adminMiddleware = compose(
verifyAccessToken,
checkPermission(PERMISSIONS.ADMIN)
);
// Use in route definition
fastify.get('/admin/dashboard', {
preHandler: adminMiddleware
}, handler);
|
Built-in Composed Middleware
authenticateAndAuthorize: Combines authentication and authorization
| JavaScript |
|---|
| const { authenticateAndAuthorize } = require('../app/handlers/middlewares');
const { PERMISSIONS } = require('../app/constants');
// Use in route definition
fastify.get('/admin/users', {
preHandler: authenticateAndAuthorize(PERMISSIONS.VIEW_USERS)
}, handler);
|
Creating Custom Middleware
You can create custom middleware using the utility functions:
| JavaScript |
|---|
| const {
utils: {
createTokenVerifier,
createPermissionChecker,
compose,
withLogging,
withErrorHandler,
conditional
}
} = require('../app/handlers/middlewares');
// Create a custom token verifier
const verifyApiKeyToken = createTokenVerifier('API_KEY', {
errorMessage: 'Invalid API key',
userFetcher: async (userRepository, apiKey) => {
return await userRepository.findByApiKey(apiKey);
}
});
// Create a conditional middleware
const skipInDevelopment = conditional(
() => process.env.NODE_ENV !== 'development',
checkPermission(PERMISSIONS.ADMIN)
);
// Create a middleware with logging
const loggingMiddleware = withLogging(
async function myMiddleware(request, reply) {
// Middleware logic
},
{ name: 'myMiddleware' }
);
// Create a middleware with error handling
const errorHandlingMiddleware = withErrorHandler(
async function riskyMiddleware(request, reply) {
// Middleware that might throw errors
},
async function errorHandler(error, request, reply) {
// Custom error handling
}
);
// Compose all middleware
const customMiddleware = compose(
verifyApiKeyToken,
skipInDevelopment,
loggingMiddleware,
errorHandlingMiddleware
);
|
Middleware Utilities
Token Verifier Utilities
createTokenVerifier(tokenType, options): Creates a token verification middleware
verifyToken(request, options): Verifies a JWT token
validateTokenPayload(payload, tokenType, errorMessage): Validates token payload
fetchUserFromToken(userRepository, userId, options): Fetches user from database
Middleware Composer Utilities
compose(...middlewares): Composes multiple middleware functions
conditional(condition, middleware): Creates a conditional middleware
withErrorHandler(middleware, errorHandler): Adds error handling to middleware
withLogging(middleware, options): Adds logging to middleware
withCache(middleware, cacheKeyGenerator, options): Adds caching to middleware
Permission Checker Utilities
createPermissionChecker(requiredPermissions, options): Creates a permission checker middleware
hasPermission(user, requiredPermissions, requireAll): Checks if user has permissions
Best Practices
- Use Composition: Compose middleware from smaller, reusable functions
- Add Logging: Use
withLogging to add logging to your middleware
- Handle Errors: Use
withErrorHandler to handle errors consistently
- Be Conditional: Use
conditional to skip middleware based on conditions
- Keep It Simple: Each middleware should do one thing well
- Reuse: Create reusable middleware functions for common tasks
- Test: Write tests for your middleware functions
Migration from Legacy Middleware
The legacy middleware functions are still available for backward compatibility:
authenticate → verifyAccessToken
verifyOTPToken → verifyOTPToken (same name, new implementation)
verifyPassToken → verifyTempPassToken
verifyRefreshToken → verifyRefreshToken (same name, new implementation)
authorize → checkPermission
To migrate to the new system:
| JavaScript |
|---|
| // Legacy
fastify.get('/protected', {
preHandler: [authenticate, authorize(PERMISSIONS.VIEW_USERS)]
}, handler);
// New
fastify.get('/protected', {
preHandler: authenticateAndAuthorize(PERMISSIONS.VIEW_USERS)
}, handler);
|
Examples
Basic Authentication
| JavaScript |
|---|
| const { verifyAccessToken } = require('../app/handlers/middlewares');
fastify.get('/profile', {
preHandler: verifyAccessToken
}, handler);
|
Role-Based Access Control
| JavaScript |
|---|
| const { authenticateAndAuthorize } = require('../app/handlers/middlewares');
const { PERMISSIONS } = require('../app/constants');
fastify.get('/admin/users', {
preHandler: authenticateAndAuthorize(PERMISSIONS.VIEW_USERS)
}, handler);
fastify.post('/admin/users', {
preHandler: authenticateAndAuthorize(PERMISSIONS.CREATE_USERS)
}, handler);
fastify.put('/admin/users/:id', {
preHandler: authenticateAndAuthorize(PERMISSIONS.UPDATE_USERS)
}, handler);
fastify.delete('/admin/users/:id', {
preHandler: authenticateAndAuthorize(PERMISSIONS.DELETE_USERS)
}, handler);
|
Complex Workflow
| JavaScript |
|---|
| const {
verifyAccessToken,
checkPermission,
utils: { compose, conditional, withLogging }
} = require('../app/handlers/middlewares');
const { PERMISSIONS } = require('../app/constants');
// Check if user is an admin
const isAdmin = async function(request) {
return request.userFromToken?.role?.name === 'admin';
};
// Skip permission check for admins
const checkPermissionUnlessAdmin = conditional(
async function(request) {
return !(await isAdmin(request));
},
checkPermission(PERMISSIONS.VIEW_SENSITIVE_DATA)
);
// Log access to sensitive data
const logSensitiveAccess = withLogging(
async function(request, reply) {
const { userFromToken } = request;
this.diScope.resolve('logger').info('Sensitive data accessed', {
userId: userFromToken.id,
username: userFromToken.username,
role: userFromToken.role?.name
});
},
{ name: 'logSensitiveAccess' }
);
// Compose middleware
const sensitiveDataMiddleware = compose(
verifyAccessToken,
checkPermissionUnlessAdmin,
logSensitiveAccess
);
// Use in route
fastify.get('/sensitive-data', {
preHandler: sensitiveDataMiddleware
}, handler);
|
Conclusion
The middleware composition system provides a flexible and reusable way to create middleware functions. By composing middleware from smaller, reusable functions, you can create complex workflows while keeping your code clean and maintainable.