express-validator如何阻止函数直接调用?自定义同类中间件实现问询
Great question! This is a super clever pattern that express-validator uses, and it's all about deferring validation logic until the request actually hits your server, instead of running it when your app starts up. Let's break this down step by step, then build a simplified version of this pattern ourselves.
The Core Idea Behind Express-Validator's Magic
When you write body('username').isEmail(), you're not actually running an email check right away. Here's what's really happening:
body('username')creates a "validation chain" object: This object keeps track of which field you're targeting (username) and starts with an empty list of validation rules.isEmail()adds a rule to the chain: Instead of executing validation, this method just adds a function (the actual email-checking logic) to the chain's rule list. It returns the same chain object, which is why you can chain multiple methods likebody('password').isLength(...).isAlphanumeric().- Express converts the chain to a middleware function: When you pass the chain to
app.post(), express recognizes it as a validation chain and converts it into a standard express middleware (a function with(req, res, next)parameters). This middleware waits until a request comes in, then runs all the collected rules against the request's body, stores errors (if any), and callsnext().
In short: You're building a list of rules first, then executing them later when the request arrives.
Building Your Own Version of This Pattern
Let's create a simple custom validation library that works exactly like express-validator. We'll make it support checking field length and requiring a substring.
Step 1: Create the Validation Chain Class
This class will handle collecting rules and acting as an express middleware:
class CustomValidationChain { constructor(fieldName) { this.fieldName = fieldName; this.rules = []; // Turn this instance into an express middleware function const middleware = (req, res, next) => { const errors = []; // Run all collected rules against the request body this.rules.forEach(rule => { const errorMessage = rule(req); if (errorMessage) { errors.push(errorMessage); } }); // Store errors on the request for later use req.customValidationErrors = errors; next(); }; // Attach our rule methods to the middleware function // This lets us chain calls like .isLength() or .includes() middleware.isLength = this.isLength.bind(this); middleware.includes = this.includes.bind(this); return middleware; } // Add a rule to check field length isLength({ min, max }) { this.rules.push((req) => { const value = req.body[this.fieldName]; if (!value || value.length < min || value.length > max) { return `${this.fieldName} must be between ${min} and ${max} characters`; } return null; // No error }); return this; // Return the middleware to keep chaining } // Add a rule to check for a required substring includes(substring) { this.rules.push((req) => { const value = req.body[this.fieldName]; if (!value || !value.includes(substring)) { return `${this.fieldName} must contain the text "${substring}"`; } return null; // No error }); return this; // Keep chaining } } // Mimic express-validator's `body()` function const body = (fieldName) => { return new CustomValidationChain(fieldName); };
Step 2: Use Your Custom Middleware
Now you can use it exactly like express-validator in your routes:
const express = require('express'); const app = express(); app.use(express.json()); app.post('/signup', // Chain validation rules body('username').isLength({ min: 3, max: 15 }).includes('user'), body('password').isLength({ min: 6, max: 20 }), (req, res) => { // Check for validation errors if (req.customValidationErrors.length > 0) { return res.status(400).json({ errors: req.customValidationErrors }); } // If no errors, proceed to create the user res.json({ message: 'Signup successful!' }); } ); app.listen(3000);
Key Takeaways
- Deferred execution: Instead of running logic immediately, you collect rules first and run them later when the request arrives.
- Chaining: By returning the same object/function from each rule method, you enable clean, readable chained calls.
- Middleware compatibility: The final object/function must conform to express's middleware signature (
(req, res, next)) so it can be used in routes.
This pattern isn't just for validators either—you can use it for any middleware where you need to configure behavior with chained methods before executing logic at request time.
内容的提问来源于stack exchange,提问作者Glenn

