Sanitizing input in Express using express-validator
By Flavio Copes
Learn how to sanitize input in your Express app with express-validator, chaining methods like trim(), escape() and normalizeEmail() after validation.
You’ve seen how to validate input that comes from the outside world to your Express app.
There’s one thing you quickly learn when you run a public-facing server: never trust the input.
Even if you sanitize and make sure that people can’t enter weird things using client-side code, you’ll still be subject to people using tools (even just the browser devtools) to POST directly to your endpoints.
Or bots trying every possible combination of exploit known to humans.
What you need to do is sanitize your input.
The express-validator package you already use to validate input can also sanitize it. The code below uses Express 5, the current major, and express-validator 7:
npm install express@5 express-validator@7
The examples use ES modules, so add "type": "module" to your package.json. If you use CommonJS, swap each import for a require(). Everything else stays the same.
Say you have a POST endpoint that accepts the name, email and age parameters:
import express from 'express'
const app = express()
app.use(express.json())
app.post('/form', (req, res) => {
const name = req.body.name
const email = req.body.email
const age = req.body.age
})
Keep express.json() in your app, because in Express 5 req.body is undefined without a body parser.
You might validate it using body():
import express from 'express'
import { body } from 'express-validator'
const app = express()
app.use(express.json())
app.post('/form', [
body('name').isLength({ min: 3 }),
body('email').isEmail(),
body('age').isNumeric()
], (req, res) => {
const name = req.body.name
const email = req.body.email
const age = req.body.age
})
You can add sanitization by chaining the sanitization methods after the validation ones:
app.post('/form', [
body('name').isLength({ min: 3 }).trim().escape(),
body('email').isEmail().normalizeEmail(),
body('age').isNumeric().trim().escape()
], (req, res) => {
//...
})
The sanitized values replace the originals in req.body. In the handler, req.body.name is already trimmed and escaped.
The chain runs in order. Here isNumeric() runs before trim(), so " 42 " fails validation. If you expect stray whitespace, put trim() first.
Here I used these methods:
trim()trims characters (whitespace by default) at the beginning and at the end of a stringescape()replaces<,>,&,',",/,\and backticks with their corresponding HTML entitiesnormalizeEmail()canonicalizes an email address. It accepts several options to lowercase email addresses or subaddresses (e.g.[email protected])
Other sanitization methods:
blacklist()removes characters that appear in the blacklistwhitelist()removes characters that do not appear in the whitelistunescape()turns those HTML entities back into<,>,&,',",/,\and backticksltrim()is liketrim(), but only trims characters at the start of the stringrtrim()is liketrim(), but only trims characters at the end of the stringstripLow()removes ASCII control characters, which are normally invisible
These ones force the conversion to a format:
toBoolean()converts the input string to a boolean. Everything except for ‘0’, ‘false’ and ” returns true. In strict mode only ‘1’ and ‘true’ return truetoDate()converts the input string to a date, or null if the input is not a datetoFloat()converts the input string to a float, or NaN if the input is not a floattoInt()converts the input string to an integer, or NaN if the input is not an integer
Like with custom validators, you can create a custom sanitizer.
In the callback function, return the sanitized value:
const sanitizeValue = value => {
//sanitize...
}
app.post('/form', [
body('value').customSanitizer(value => {
return sanitizeValue(value)
}),
], (req, res) => {
const value = req.body.value
})Want me to talk about your product? You can sponsor this site.