Server-side validation
Why server-side, not client-side
Mongoat validates documents by handing MongoDB a $jsonSchema validator, applied to the collection itself — it does not run its own client-side validation pass before a write. That's a deliberate choice, not a missing feature. Client-side validation only protects writes that happen to go through Mongoat; a $jsonSchema validator attached to the collection is enforced by the MongoDB server itself, for every write from any client — another service, a migration script, mongosh, a teammate's ad hoc tool. Server-side validation is the single source of truth for what a document in that collection is allowed to look like, independent of which code path produced the write.
This also keeps Mongoat's dependency footprint minimal: no client-side schema-validation library to bundle, version and keep in sync with what the server actually enforces — one schema, enforced in one place.
How Mongoat builds the validator
When you construct a Model with a schema, schemaValidatorBuilder (private to Model) turns it into the shape MongoDB's $jsonSchema validator expects:
- Clone the schema. The schema object is
structuredCloned before anything else touches it. Schemas are often declared once and reused across models or modules; mutating the caller's own object in place would leak changes back to code that never asked for them. - Inject
_id. Every validator gets an_id: { bsonType: 'objectId' }property and_idadded torequired, so the schema you write only needs to describe your own fields. - Force
additionalProperties: false, recursively. MongoDB does not default to rejecting unknown properties — a schema withoutadditionalProperties: falsewould silently accept extra fields nobody declared. Mongoat walks the schema (including nestedpropertiesand arrayitems) and setsadditionalProperties: falseat every object level that doesn't already specify it, so an unlisted field is rejected wherever it appears in the document, not just at the top level. - Merge in
validationQueryExpressions, if provided, alongside the$jsonSchemakey — for validation rules expressed as MongoDB query operators rather than JSON Schema keywords.
The result — { validationAction: 'error', validationLevel: 'strict', validator: { $jsonSchema: {...} } } — is stored on the Model instance and applied by Database, never evaluated in application code.
How it's applied: collMod, not an in-process check
Database.setupCollection() (invoked by setupCollections() for every registered model) calls setupValidators(), which runs:
await this[kDb]?.command({
collMod: model.collectionName,
validator: model.validator,
validationAction: model.validationAction,
validationLevel: model.validationLevel,
});collMod is a MongoDB administrative command that attaches the validator to the collection itself, at the database level. From that point on, insert, update, insertMany, bulkWrite — issued by Mongoat, by the raw driver through the escape hatch, or by anything else talking to that collection — are checked by the MongoDB server against the same schema. Mongoat's CRUD methods never run a parallel "does this match the schema" check in JavaScript before calling the driver; the server is the only validator, which is exactly why the guarantee holds regardless of which client made the write.
Trade-off vs. client-side validation
Client-side validation libraries (e.g. class-validator-style decorators) are explicitly out of scope for Mongoat. The trade-off:
- No duplicated logic. The schema is defined once, as
$jsonSchema, and is the same artifact enforced by the server — there's no second client-side copy of the rules that can drift out of sync with it. - No extra dependency. Consistent with the thin-ODM minimal-dependencies philosophy — no validation engine bundled beyond what the MongoDB driver already ships.
- Coverage that isn't tied to the ODM. Because enforcement lives on the collection, it protects every write path, not only the ones that happen to go through a
Modelmethod. - What you give up: validation errors surface as a MongoDB server error on write (wrapped by Mongoat's error hierarchy — see Handle errors) rather than as an immediate, synchronous, field-level client-side error before any network round-trip. For an app that wants pre-submission form validation, that's a complementary concern layered on top of — not a replacement for — the server-side guarantee Mongoat provides.
See also
- Define indexes & validation — the how-to for declaring a
schemaand enablingvalidity: true. - Why Proxy gating — the other mechanism
validity: truealso configures (allowedMethods). - Handle errors — how validation failures surface through the
MongoatErrorhierarchy. - Reference —
ModelValidationSchema,ModelDbValidationProps.