Node.js · 08/19 6 min
Reading request input safely
params, query and body: everything is a string until you parse it, and some of it is hostile.
Where input lives
| Example | Type you get | |
|---|---|---|
req.params |
/orders/:id |
string |
req.query |
?page=2&tag=a&tag=b |
string, array if repeated, object with the extended parser |
req.body |
JSON body | whatever JSON had, only if express.json() ran first |
req.headers |
authorization |
string, keys lowercased |
Strings that look like numbers
req.query.page + 1; // "21"
Number(""); // 0
Number("abc"); // NaN, and NaN comparisons are always false
parseInt("10abc", 10); // 10, silently
Parse once, validate, then use the clean value:
function intParam(v, { def, min, max }) {
if (v === undefined) return def;
const n = Number(v);
if (!Number.isInteger(n) || n < min || n > max) throw new BadRequest(`invalid number: ${v}`);
return n;
}
const page = intParam(req.query.page, { def: 1, min: 1, max: 10_000 });
const limit = intParam(req.query.limit, { def: 20, min: 1, max: 100 });
const skip = (page - 1) * limit; // page 1 → skip 0
Objects where you expected strings
With Express 4's default query parser, ?email[$ne]=x arrives as { $ne: "x" }. Passed straight into a Mongo filter, that matches every user.
if (typeof req.query.email !== "string") throw new BadRequest("email must be a string");
Same for search: escape user text before building a RegExp (q.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")), or a .* query returns everything and a ( throws.
Validate the body shape
- Required fields present, correct types, sane lengths.
- Allow-list fields you copy:
const { name, price } = req.body, neverObject.assign(doc, req.body)(that lets a client setrole: "admin"). - Normalise before comparing:
email.trim().toLowerCase(). - Bad input is 400 with a clear message, not a 500 from deep inside the model.
Practise it now
Next in Node.js
Auth: hashing, JWTs, 401 vs 403