docs: rewrite documents docs - #16298
Conversation
There was a problem hiding this comment.
Pull request overview
This PR rewrites the Mongoose “Documents” guide to better explain common confusion points around hydrated vs lean documents, casting vs validation, save() change tracking, and middleware behavior. It also includes a small documentation example update for Schema.Types.Date.checkRequired() and a minor formatting tweak in the docs “copy code” script.
Changes:
- Rewrote
docs/documents.mdwith expanded explanations and new examples for hydration,save(), casting/validation, required behavior, and middleware. - Updated the
SchemaDate.checkRequired()JSDoc example to focus on disallowing invalid dates. - Adjusted an async arrow style in
docs/js/copy-code.js.
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| lib/schema/date.js | Updates the Date schematype checkRequired() documentation example. |
| docs/js/copy-code.js | Minor formatting tweak to the async arrow function used for copy-to-clipboard. |
| docs/documents.md | Major rewrite of the Documents guide with new structure and examples. |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: hasezoey <hasezoey@gmail.com>
…mongoose into vkarpov15/new-documents-docs
AbdelrahmanHafez
left a comment
There was a problem hiding this comment.
Dumping my thought process as I try to read this from a fresh reader's perspective:
Mongoose documents are JavaScript objects backed by MongoDB data
I'm worried this could be misinterpreted as POJOs (the actual POJOs with the data in the mongo server). The next sentence and the Hydrated Documents vs Lean Documents section clarify it, but I think it's worth avoiding the misunderstanding in the first place. I'd change JavaScript objects to Document class instances. They're technically the same thing, but one can be mistaken for POJOs.
They are instances of Mongoose's Document class
...later, in "What is a Document":
A Mongoose document is an instance of a Model. When you instantiate a model, you create a new document.
This is a bit confusing on first read: are they instances of a Document or a Model? How do Model and Document relate?
Do we assume the reader of this page has already read the Models doc, or should this page stand on its own without having to go somewhere else to understand the relationship?
You can use the lean() method to make Mongoose queries return POJOs instead of hydrated documents.
Would be nice to briefly explain when I'd reach for lean() over hydrated docs, the performance benefit for read-only cases, and the drawbacks. If it's covered in detail elsewhere we can link to it, or just re-explain here.
Nitpick on the Setting Nested Properties code example:
const schema = new Schema({
nested: {
subdoc: new Schema({
name: String
})
}
});
const TestModel = mongoose.model('Test', schema);
const doc = new TestModel();
doc.set('nested.subdoc.name', 'John Smith');
doc.nested.subdoc.name; // 'John Smith'A newcomer might think subdoc and nested have meaningful distinction rather than just being generic path names. I'd rewrite this with a realistic example. More generally: using different realistic examples across the file helps people who learn by pattern recognition see what's constant and what's variable.
On this snippet, we tell readers not to use nullish coalescing assignments without explaining why it works in the first case but not the second:
// The following works fine
const doc3 = new TestModel();
doc3.nested.subdoc ??= {};
doc3.nested.subdoc.name = 'John Smythe';
// The following does **NOT** work.
// Do not use the following pattern with Mongoose documents.
const doc4 = new TestModel();
(doc4.nested.subdoc ??= {}).name = 'Charlie Smith';
doc4.nested.subdoc; // Empty object
doc4.nested.subdoc.name; // undefined.I honestly haven't seen this pattern used in the wild. We should either explain more (why it works in the first case, why not in the second, ideally the root cause), or drop it entirely given how rare it is. I'm leaning towards keeping it but explaining more.
Very nitpicky: the save() lifecycle being an image looks a bit off, especially on the dark theme. Probably fine for now, but might be worth figuring out how to inject custom HTML into the docs later if we don't have an easy way to do it.
Maybe we could consider switching the image to use Mermaid? (it is natively supported in github comments and github markdown display, though we would likely need another dependency for the website OR statically generate the html once) |
|
"A newcomer might think subdoc and nested have meaningful distinction rather than just being generic path names." <-- they do have a meaningful distinction, but just not in this context, so you're right to call that out as a red herring. |
My preference here would be some native HTML, gives us a lot more control over the styling, but Mermaid also works. ---
title: "save() Lifecycle"
---
flowchart TB
A("`**🛡️ 1. Pre-Validate Middleware**
Runs pre('validate') middleware.
Use this to prepare or modify the document before validation.`")
B("`**🔍 2. Validate**
Mongoose casts values and runs built-in validators and custom validators.
_If validation fails, save() is aborted and no changes are written._`")
C("`**✅ 3. Post-Validate Middleware**
Runs post('validate') middleware.
Runs whether validation succeeds or fails.
Use this for logging, metrics, etc.`")
D("`**⚙️ 4. Pre-Save Middleware**
Runs pre('save') middleware.
Use this for tasks that should run right before the document is persisted (e.g. hashing a password, setting timestamps).`")
E[("`**🗄️ 5. Write to MongoDB**
Mongoose generates the appropriate write operation:
• New document: insertOne()
• Existing document: updateOne() with only the modified paths`")]
F("`**📥 6. Post-Save Middleware**
Runs post('save') middleware.
Runs after the write attempt.
The document is now up to date with what's in MongoDB.`")
A --> B --> C --> D --> E --> F
classDef purple fill:#EDE7F6,stroke:#7E57C2,stroke-width:2px,color:#311B92;
classDef green fill:#E8F5E9,stroke:#43A047,stroke-width:2px,color:#1B5E20;
classDef orange fill:#FFF3E0,stroke:#FB8C00,stroke-width:2px,color:#E65100;
classDef blue fill:#E3F2FD,stroke:#1E88E5,stroke-width:2px,color:#0D47A1;
class A,C,F purple;
class B green;
class D orange;
class E blue;
|
Co-authored-by: Hafez <a.hafez852@gmail.com>
Co-authored-by: Hafez <a.hafez852@gmail.com>
|
@AbdelrahmanHafez how do you like this dark theme image?
|
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
IslandRhythms
left a comment
There was a problem hiding this comment.
Left a couple comments but LGTM otherwise
…ences between documents vs POJOs re JS internals

Summary
I've always felt like our existing Documents guide is one of the weakest pages on the documentation, so I went ahead and rewrote it. I tried to highlight some more common pain points and points of confusion:
save()works especially with change trackingupdateOne()etc. not triggeringsave()middlewareExamples