Bird
Raised Fist0
Expressframework~10 mins

Custom validation rules in Express - Step-by-Step Execution

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
Concept Flow - Custom validation rules
Receive request data
Apply built-in validations
Apply custom validation function
Is data valid?
NoSend error response
Yes
Proceed to next middleware or handler
The flow shows how Express receives data, applies built-in and custom validations, then either sends errors or continues processing.
Execution Sample
Express
const { body, validationResult } = require('express-validator');

app.post('/user', [
  body('age').custom(value => {
    if (value < 18) throw new Error('Must be 18 or older');
    return true;
  })
], (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) return res.status(400).json({ errors: errors.array() });
  res.send('User valid');
});
This code adds a custom validation to check if age is 18 or older, then sends errors or success.
Execution Table
StepActionInput ValueValidation ResultNext Step
1Receive request with age=1616Not validated yetApply built-in validations
2Apply built-in validations16Pass (no built-in rules here)Apply custom validation
3Run custom validation function16Fail: 'Must be 18 or older'Send error response
4Send error responseN/AResponse 400 with error messageEnd
5Receive request with age=2020Not validated yetApply built-in validations
6Apply built-in validations20PassApply custom validation
7Run custom validation function20PassProceed to handler
8Send success responseN/AResponse 200 'User valid'End
💡 Execution stops after sending response: either error if validation fails or success if passes.
Variable Tracker
VariableStartAfter Step 3 (age=16)After Step 7 (age=20)Final
ageundefined162020 or 16 depending on request
validationResult.errorsempty[{msg:'Must be 18 or older'}]emptyErrors or empty array
response.statusunset400200Final HTTP status code
response.bodyunset{"errors":[{"msg":"Must be 18 or older"}]}"User valid"Final response content
Key Moments - 3 Insights
Why does the custom validation function throw an error instead of returning false?
Throwing an Error inside the custom validator signals express-validator that validation failed with a message. Returning false would not provide the error message. See execution_table row 3.
What happens if validationResult finds errors?
If errors exist, the code sends a 400 response with error details and stops further processing. See execution_table row 4.
Can custom validation access other request fields?
Yes, the custom validator can access req object if needed, but here it only checks the value directly. This is common for cross-field checks.
Visual Quiz - 3 Questions
Test your understanding
Look at the execution table, what validation result occurs at step 3 when age is 16?
AFail with message 'Must be 18 or older'
BPass
CNo validation performed
DThrows a system error
💡 Hint
Check the 'Validation Result' column in row 3 of the execution_table.
At which step does the server send a success response for age 20?
AStep 4
BStep 8
CStep 6
DStep 7
💡 Hint
Look for 'Send success response' action in the execution_table.
If the custom validator returned false instead of throwing an error, what would happen?
AValidation would fail with the same error message
BValidation would pass silently
CValidation would fail but no error message would be shown
DServer would crash
💡 Hint
Recall key_moments explanation about throwing Error vs returning false.
Concept Snapshot
Custom validation rules in Express use functions that throw errors to signal validation failure.
Use express-validator's body().custom() to add these rules.
If validation fails, send error response with details.
If passes, continue to next middleware or handler.
This allows flexible checks beyond built-in validators.
Full Transcript
This visual execution trace shows how Express handles custom validation rules using express-validator. First, the server receives request data. Built-in validations run, then custom validation functions check specific conditions. If the custom validator throws an error, validation fails and the server sends a 400 error response with messages. If validation passes, the server proceeds to handle the request normally and sends a success response. Variables like age, validation errors, and response status update step-by-step. Key moments clarify why throwing errors is needed for validation failure and how errors stop request processing. The quiz tests understanding of validation results and response steps. This helps beginners see exactly how custom validation works in Express.

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