Security
Input Validation and Query Injection
Operator injection through request bodies, and why you never pass user JSON straight to find().
SQL injection is string concatenation. MongoDB injection is letting the client supply query operators. find(req.body) treats { email: { $ne: null } } as a filter, not as an email. You did not concatenate; you still lost.
// Wrong — body is the query
await users.find(req.body).toArray()
// Right — you choose the keys and types
const email = typeof req.query.email === "string" ? req.query.email : ""
await users.find({ email }).toArray()import { ObjectId } from "mongodb";
if (!ObjectId.isValid(req.params.id)) return res.status(400).end()
const user = await users.findOne({ _id: new ObjectId(req.params.id) })- Allowlist fields. Only
email,city,statusyou named — never spreadreq.bodyintofindor$set. - Scalars. Coerce to string/number/boolean. If
typeof email === 'object', reject. - Updates.
$set: req.bodyis the same class of bug: a client-sent$renameor nested operator. Pick fields. - `$where` / `$function`. Server-side JS in queries. Do not enable; do not take them from users. Practice sandboxes reject them for this reason.
Mongoose strict strips unknown schema paths on save; it does not make User.find(req.body) safe. Collection $jsonSchema does not parse HTTP. Validation is your filter builder. Login must not be findOne({ email, password }) with operator-shaped password — compare a hash in the app after loading by email only.
Interview question
What is NoSQL / MongoDB query injection, and how do you prevent it?
Passing client JSON into find() or update() so they can send operators like $ne or $gt instead of a normal value. Prevent it by building the filter yourself from validated scalars — strings, numbers, ObjectId — never req.body as the query. Allowlist fields on updates. Disable server-side JS operators. Mongoose does not make find(req.body) safe.