Validation
Mondel builds Zod schemas from your defineSchema definitions and runs them on write paths.
Modes
Configured on createClient:
createClient({
schemas,
validation: "strict", // default | "loose" | "off"
});| Mode | Behavior | Create defaults |
|---|---|---|
strict | Invalid data throws ZodError | Applied |
loose | Logs a warning; operation continues with the original values | Applied |
off | No runtime validation (TypeScript still helps at compile time) | Not applied |
off is a full bypass: no validation and no defaults. If you only want to stop writes from throwing, use loose — it keeps writing the same document shape as strict.
What is validated
| Input | Validated |
|---|---|
create / createMany body | Yes |
Plain update object { name: "x" } | Yes |
$set / $setOnInsert, whole fields | Yes |
$set with dot-notation keys ("profile.level") | No |
Other operators ($inc, $push, $unset, …) | No |
bulkWrite operations | No |
| Read results | No |
Dot-notation is not validated
The schema shape is keyed by field name, so a dot-notation key is an unknown key and passes through untouched. These two writes are not equivalent:
// validated — `profile` is a declared field
await db.users.updateOne(filter, { $set: { profile: { level: "oops" } } }); // throws
// not validated — "profile.level" is an unknown key
await db.users.updateOne(filter, { $set: { "profile.level": "oops" } }); // passesTreat Mondel validation as a guardrail on whole-field writes, not a guarantee that everything reaching the collection matches the schema. For hard enforcement, install database-level validators (see below).
Defaults
Defaults are a property of the schema, not of a successful parse:
- On create, field
.default(...)values are applied at all nesting levels (nested objects and object items inside arrays) instrictandloose— including when aloosepayload fails validation. - On update, defaults are not applied at any level — partial updates must not inject missing fields.
- When
validation: "off", defaults are not applied.
Non-primitive defaults (.default([]), .default({})) are cloned per document, so two documents never share the same array or object instance.
Unknown keys are kept (passthrough) so MongoDB open documents stay compatible.
Manual Zod access
import { zodSchema, zodCreateSchema, zodUpdateSchema, validate } from "mondel";
const createZ = zodCreateSchema(userSchema);
const result = validate(createZ, payload);
if (!result.success) {
console.error(result.errors);
}Use this for HTTP body validation before calling Mondel, or to validate legacy reads.
Performance
- Validators are cached per collection proxy (built once).
- Cost is on writes only.
- Use
validation: "off"only in trusted internal paths if profiling requires it.
Errors
import { ZodError } from "zod";
try {
await db.users.create({ email: "not-an-email" });
} catch (e) {
if (e instanceof ZodError) {
// e.issues — field paths and messages
}
}Server-side validators
CLI push --apply-validators installs MongoDB $jsonSchema from the same field definitions. That is database-level enforcement, independent of the Node validation mode. See CLI.
