Bird
Raised Fist0
Expressframework~15 mins

Custom validation rules in Express - Deep Dive

Choose your learning style10 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
Overview - Custom validation rules
What is it?
Custom validation rules in Express let you check if data sent by users meets your specific needs. Instead of only using built-in checks, you write your own rules to decide what is right or wrong. This helps keep your app safe and working well by catching mistakes or bad data early. It’s like setting your own standards for what information is allowed.
Why it matters
Without custom validation, your app might accept wrong or harmful data, causing errors or security problems. Custom rules let you control exactly what data is okay, preventing bugs and protecting users. Imagine a form that only accepts phone numbers in a certain format or passwords that must be strong. Without these checks, users could cause trouble or your app could crash.
Where it fits
Before learning custom validation rules, you should know basic Express setup and how to handle requests and responses. After this, you can learn about error handling and middleware to manage validation results better. Later, you might explore libraries like express-validator or Joi that help build validations more easily.
Mental Model
Core Idea
Custom validation rules are your own tests that check if user data fits your app’s special needs before you accept it.
Think of it like...
It’s like a security guard at a club who checks not just ID but also if you’re wearing the right dress code or have a membership card before letting you in.
┌─────────────────────────────┐
│ Incoming User Data          │
└─────────────┬───────────────┘
              │
              ▼
┌─────────────────────────────┐
│ Custom Validation Rules     │
│ (Your own checks)           │
└─────────────┬───────────────┘
              │ Pass or Fail
              ▼
┌─────────────┴───────────────┐
│ Accept Data or Send Error   │
└─────────────────────────────┘
Build-Up - 7 Steps
1
FoundationUnderstanding basic validation needs
🤔
Concept: Learn why validating user data is important in web apps.
When users send data to your Express app, it can be wrong or harmful. Basic validation checks if required fields exist and if data types match, like making sure an email looks like an email. This prevents crashes and bad behavior.
Result
You know why you must check user data before using it.
Understanding the risks of unchecked data helps you see why validation is a must-have for any app.
2
FoundationUsing middleware for validation
🤔
Concept: Learn how Express middleware can run validation code before your main logic.
Middleware functions in Express run in order for each request. You can write a middleware that checks data and stops the request if data is invalid. For example, a middleware that checks if 'username' exists in the request body.
Result
You can intercept requests and reject bad data early.
Knowing middleware order lets you control when and how validation happens in your app.
3
IntermediateWriting simple custom validation functions
🤔Before reading on: do you think a custom validation function should return true/false or throw errors? Commit to your answer.
Concept: Create your own functions that check specific rules on data fields.
A custom validation function takes input and returns true if valid or false if not. For example, a function that checks if a password has at least 8 characters and a number. You call this function inside middleware to decide if data passes.
Result
You can enforce rules that built-in checks don’t cover.
Understanding return values from validation functions helps you design clear and reusable checks.
4
IntermediateIntegrating validation with request handling
🤔Before reading on: do you think validation should happen before or after processing data? Commit to your answer.
Concept: Use validation results to decide whether to continue or send an error response.
Inside your middleware, after running validations, if any fail, you send a response with an error message and stop further processing. If all pass, you call next() to continue. This keeps your app safe and user-friendly.
Result
Your app only processes good data and informs users about mistakes.
Knowing when to stop request processing prevents wasted work and confusing errors.
5
IntermediateHandling asynchronous validation rules
🤔Before reading on: do you think validation functions can be asynchronous? Commit to your answer.
Concept: Some validations need to check external sources, so they must be async.
For example, checking if a username is already taken requires a database query. Your validation function returns a Promise. Middleware uses async/await to wait for results before deciding to continue or reject.
Result
You can validate data that depends on external info safely.
Understanding async validation prevents bugs where checks run too late or not at all.
6
AdvancedBuilding reusable validation middleware
🤔Before reading on: do you think validation middleware should be specific or generic? Commit to your answer.
Concept: Create middleware that can accept different rules and apply them flexibly.
Instead of writing one middleware per route, build a function that takes validation rules as parameters and returns middleware. This lets you reuse code and keep your app DRY (Don’t Repeat Yourself).
Result
You write less code and maintain validations easily across routes.
Knowing how to abstract validation logic improves code quality and scalability.
7
ExpertCombining custom rules with validation libraries
🤔Before reading on: do you think libraries replace custom rules or complement them? Commit to your answer.
Concept: Use libraries like express-validator to handle common checks and add your own rules for special cases.
Libraries provide many built-in validators and error formatting. You can add custom validators by defining functions and integrating them into the library’s chain. This gives you power and convenience together.
Result
Your app has robust, readable, and maintainable validation logic.
Understanding how to extend libraries with custom rules unlocks professional-grade validation.
Under the Hood
Express processes requests through middleware functions in order. Validation middleware inspects the request data, runs your custom checks synchronously or asynchronously, and decides whether to call next() to continue or send an error response. When async validations are used, Express waits for Promises to resolve before proceeding. This flow ensures only valid data reaches your main logic.
Why designed this way?
Express uses middleware to keep code modular and flexible. Custom validation fits naturally as middleware so developers can insert their own rules anywhere in the request pipeline. This design avoids monolithic code and lets apps handle diverse validation needs without changing Express core.
┌───────────────┐
│ HTTP Request  │
└───────┬───────┘
        │
        ▼
┌─────────────────────┐
│ Validation Middleware│
│ - Runs custom rules  │
│ - Sync or Async      │
└───────┬─────────────┘
        │ Pass or Fail
        ▼
┌───────────────┐    ┌───────────────┐
│ next() called │    │ Error response│
│ Continue app  │    │ Stop request  │
└───────────────┘    └───────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Do you think custom validation rules must always throw errors to stop processing? Commit to yes or no.
Common Belief:Custom validation rules have to throw errors to indicate failure.
Tap to reveal reality
Reality:Custom validation functions usually return true/false or a Promise resolving to that; throwing errors is optional and less common.
Why it matters:Throwing errors unnecessarily can cause unhandled exceptions and crash your app instead of cleanly rejecting bad data.
Quick: Do you think validation middleware runs after your route handler? Commit to yes or no.
Common Belief:Validation middleware runs after the main route handler.
Tap to reveal reality
Reality:Validation middleware must run before the route handler to prevent processing invalid data.
Why it matters:Running validation too late means your app might process bad data, causing bugs or security holes.
Quick: Do you think all validation can be done synchronously? Commit to yes or no.
Common Belief:All validation rules can be synchronous checks.
Tap to reveal reality
Reality:Some validations require async operations like database lookups, so they must be asynchronous.
Why it matters:Ignoring async needs leads to race conditions or skipped checks, letting bad data through.
Quick: Do you think libraries like express-validator replace the need for custom rules? Commit to yes or no.
Common Belief:Validation libraries cover all validation needs, so custom rules are unnecessary.
Tap to reveal reality
Reality:Libraries cover common cases but custom rules are needed for app-specific logic.
Why it matters:Relying only on libraries limits flexibility and can cause incorrect validation for unique requirements.
Expert Zone
1
Custom validation functions can be composed to build complex rules from simple ones, improving maintainability.
2
Validation middleware order matters: placing it too late or after error handlers breaks validation flow.
3
Express’s error handling middleware can be used to centralize validation error responses, keeping route code clean.
When NOT to use
Avoid writing custom validation rules for very common checks like email format or length constraints; use established libraries like express-validator or Joi instead. Also, for very complex schemas, schema validation libraries are better than manual custom rules.
Production Patterns
In production, developers combine express-validator for standard checks with custom async validators for database constraints. Validation middleware is reused across routes via parameterized functions. Errors are formatted uniformly and sent with HTTP 400 status. Validation logic is tested separately to ensure reliability.
Connections
Middleware pattern
Custom validation rules are implemented as middleware in Express.
Understanding middleware helps grasp how validation fits into request processing and how to control flow.
Functional programming
Custom validation functions are pure functions that return true/false or Promises.
Knowing functional programming principles helps write clean, reusable validation logic.
Quality control in manufacturing
Validation rules act like quality checks on products before shipping.
Seeing validation as quality control clarifies its role in preventing defects and ensuring reliability.
Common Pitfalls
#1Writing validation that throws errors instead of returning results.
Wrong approach:function validateEmail(email) { if (!email.includes('@')) { throw new Error('Invalid email'); } return true; }
Correct approach:function validateEmail(email) { return email.includes('@'); }
Root cause:Misunderstanding how Express middleware handles errors and expecting exceptions to control flow.
#2Placing validation middleware after route handlers.
Wrong approach:app.get('/user', (req, res) => { res.send('User page'); }); app.use(validationMiddleware);
Correct approach:app.use(validationMiddleware); app.get('/user', (req, res) => { res.send('User page'); });
Root cause:Not understanding Express middleware order and that validation must happen before processing.
#3Ignoring async validations and writing only synchronous checks.
Wrong approach:function validateUsername(username) { return username !== 'taken'; } // No async
Correct approach:async function validateUsername(username) { const exists = await db.findUser(username); return !exists; }
Root cause:Not realizing some validations require external data and must be asynchronous.
Key Takeaways
Custom validation rules let you enforce your app’s unique data requirements beyond basic checks.
Express middleware is the natural place to run validation before your main logic processes data.
Validation functions should return clear results and handle async needs properly to avoid bugs.
Combining custom rules with validation libraries gives you both power and convenience.
Proper middleware order and error handling ensure your app stays safe and user-friendly.

Practice

(1/5)
1. What is the main purpose of using custom() in Express validation?
easy
A. To format the response JSON
B. To automatically sanitize all inputs
C. To connect to the database
D. To create your own rules for checking input values

Solution

  1. Step 1: Understand the role of custom()

    The custom() method allows you to write your own validation logic beyond built-in checks.
  2. Step 2: Identify the purpose in input validation

    It is used to check inputs with rules you define, like checking a password strength or a special format.
  3. Final Answer:

    To create your own rules for checking input values -> Option D
  4. Quick Check:

    Custom validation = custom rules [OK]
Hint: Custom means you write your own check function [OK]
Common Mistakes:
  • Thinking custom() sanitizes inputs automatically
  • Confusing custom() with response formatting
  • Assuming custom() connects to databases
2. Which of the following is the correct syntax to add a custom validation rule using Express Validator?
easy
A. check('age').custom(value => { if(value < 18) throw new Error('Too young'); return true; })
B. check('age').custom(value => value < 18 ? true : false)
C. check('age').custom(value => { return false; })
D. check('age').custom(value => { throw 'Error'; })

Solution

  1. Step 1: Review correct custom validation syntax

    The function inside custom() should throw an error if validation fails and return true if it passes.
  2. Step 2: Analyze each option

    check('age').custom(value => { if(value < 18) throw new Error('Too young'); return true; }) throws an error when value is less than 18 and returns true otherwise, which is correct. check('age').custom(value => value < 18 ? true : false) returns true when value is less than 18, which is opposite logic. check('age').custom(value => { return false; }) always returns false, which fails validation. check('age').custom(value => { throw 'Error'; }) throws an error unconditionally, so it always fails.
  3. Final Answer:

    check('age').custom(value => { if(value < 18) throw new Error('Too young'); return true; }) -> Option A
  4. Quick Check:

    Throw error on fail, return true on pass [OK]
Hint: Throw error to fail, return true to pass [OK]
Common Mistakes:
  • Returning false instead of throwing error
  • Throwing error without condition
  • Returning true on invalid input
3. Given this code snippet, what will be the validation result if req.body.username is "abc"?
check('username').custom(value => {
  if(value.length < 5) throw new Error('Too short');
  return true;
})
medium
A. Validation fails with 'Too short' error
B. Validation passes
C. Validation fails with syntax error
D. Validation passes but logs a warning

Solution

  1. Step 1: Check the input value length

    The input "abc" has length 3, which is less than 5.
  2. Step 2: Apply the custom validation logic

    The function throws an error 'Too short' if length is less than 5, so it throws an error here causing validation to fail.
  3. Final Answer:

    Validation fails with 'Too short' error -> Option A
  4. Quick Check:

    Input too short = error thrown [OK]
Hint: Check input length against condition in custom() [OK]
Common Mistakes:
  • Assuming validation passes for short input
  • Confusing error throwing with warnings
  • Expecting syntax errors from valid code
4. Identify the error in this custom validation code:
check('email').custom(value => {
  if(!value.includes('@'))
    return new Error('Invalid email');
  return true;
})
medium
A. The function must return false instead of true
B. The condition should check for '.' instead of '@'
C. It should throw an error, not return it
D. No error, code is correct

Solution

  1. Step 1: Understand error signaling in custom validation

    Custom validators must throw an error to indicate failure, not return an Error object.
  2. Step 2: Analyze the given code

    The code returns new Error('Invalid email') instead of throwing it, so validation will not fail as expected.
  3. Final Answer:

    It should throw an error, not return it -> Option C
  4. Quick Check:

    Throw error to fail validation [OK]
Hint: Throw errors, don't return them in custom() [OK]
Common Mistakes:
  • Returning Error object instead of throwing
  • Checking wrong condition for email
  • Returning false instead of throwing error
5. You want to create a custom validation rule that checks if a password contains at least one uppercase letter, one number, and is at least 8 characters long. Which of these implementations correctly achieves this?
hard
A. check('password').custom(value => { if(value.length < 8) return false; if(!/[A-Z]/.test(value)) return false; if(!/\d/.test(value)) return false; return true; })
B. check('password').custom(value => { if(!/[A-Z]/.test(value)) throw new Error('Missing uppercase'); if(!/\d/.test(value)) throw new Error('Missing number'); if(value.length < 8) throw new Error('Too short'); return true; })
C. check('password').custom(value => { if(value.length < 8) throw 'Too short'; if(!/[A-Z]/.test(value)) throw 'Missing uppercase'; if(!/\d/.test(value)) throw 'Missing number'; return false; })
D. check('password').custom(value => { if(value.length >= 8 && /[A-Z]/.test(value) && /\d/.test(value)) return true; else return false; })

Solution

  1. Step 1: Check each condition with proper error throwing

    check('password').custom(value => { if(!/[A-Z]/.test(value)) throw new Error('Missing uppercase'); if(!/\d/.test(value)) throw new Error('Missing number'); if(value.length < 8) throw new Error('Too short'); return true; }) checks each condition separately and throws a specific error if it fails, returning true only if all pass.
  2. Step 2: Compare other options for correctness

    check('password').custom(value => { if(value.length < 8) return false; if(!/[A-Z]/.test(value)) return false; if(!/\d/.test(value)) return false; return true; }) returns false instead of throwing errors, which is incorrect. check('password').custom(value => { if(value.length < 8) throw 'Too short'; if(!/[A-Z]/.test(value)) throw 'Missing uppercase'; if(!/\d/.test(value)) throw 'Missing number'; return false; }) throws string errors and returns false at the end, which breaks the rule of returning true on success. check('password').custom(value => { if(value.length >= 8 && /[A-Z]/.test(value) && /\d/.test(value)) return true; else return false; }) returns false instead of throwing an error if conditions fail, which is incorrect.
  3. Final Answer:

    check('password').custom(value => { if(!/[A-Z]/.test(value)) throw new Error('Missing uppercase'); if(!/\d/.test(value)) throw new Error('Missing number'); if(value.length < 8) throw new Error('Too short'); return true; }) -> Option B
  4. Quick Check:

    Throw specific errors, return true if all pass [OK]
Hint: Throw specific errors for each fail, return true if all pass [OK]
Common Mistakes:
  • Returning false instead of throwing errors
  • Throwing strings instead of Error objects
  • Returning false on success