CodingNic

Relationships in MongoDB

Referencing and populate

Relationships in MongoDB 15 min read

Referencing and populate

Objectives

By the end of this lesson, you should be able to:

  • Reference another document by its ObjectId instead of embedding it
  • Load a referenced document with populate
  • Explain when referencing fits better than embedding

💡 Why this matters: Last lesson’s comments belonged exclusively to one post, and embedding fit well. An author who writes many posts is a different shape, referencing, MongoDB’s version of a foreign key, is the better fit here.

⚠️ A note on verification: this tooling has no route to a local MongoDB server. The code below reflects Mongoose’s stable, current, documented API. Try it yourself, on your own machine, against a real MongoDB server.

Defining a Reference

javascript
const authorSchema = new mongoose.Schema({
  name: { type: String, required: true }
});

const postSchema = new mongoose.Schema({
  title: { type: String, required: true },
  body: { type: String, required: true },
  author: { type: mongoose.Schema.Types.ObjectId, ref: 'Author' }
});

const Author = mongoose.model('Author', authorSchema);
const Post = mongoose.model('Post', postSchema);

author: { type: mongoose.Schema.Types.ObjectId, ref: 'Author' } stores only the author’s _id inside each post, ref: 'Author' tells Mongoose which model that ID refers to, this is the direct Mongoose equivalent of Prisma’s authorId Int foreign key (Module 5).

Creating Referenced Documents

javascript
const author = await Author.create({ name: 'Robert Martin' });

const post = await Post.create({
  title: 'Clean Code Principles',
  body: 'Some thoughts on writing maintainable software.',
  author: author._id
});
console.log(post);
text
{
  _id: new ObjectId('...'),
  title: 'Clean Code Principles',
  body: 'Some thoughts on writing maintainable software.',
  author: new ObjectId('65f1...'),
  __v: 0
}

Notice author is just an ObjectId, not the full author document, exactly like a foreign key column holding just an integer in PostgreSQL.

Loading the Referenced Document With populate

javascript
const post = await Post.findOne({ title: 'Clean Code Principles' }).populate('author');
console.log(post);
text
{
  _id: new ObjectId('...'),
  title: 'Clean Code Principles',
  body: 'Some thoughts on writing maintainable software.',
  author: { _id: new ObjectId('65f1...'), name: 'Robert Martin', __v: 0 },
  __v: 0
}

.populate('author') replaces the raw ObjectId with the actual author document, in one call, this is Mongoose’s version of Prisma’s include (Module 5, Lesson 3), and without it, MongoDB would need a second, separate query to fetch the author.

Referencing the Other Direction

javascript
const authorSchema = new mongoose.Schema({
  name: { type: String, required: true },
  posts: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Post' }]
});

An Author can hold an array of post ObjectIds, and .populate('posts') loads every referenced post at once, the reverse direction of the relationship, same pattern as Author.books in Prisma (Module 5, Lesson 1).

Try It

  1. Define Author and Post models with a reference, as shown above.
  2. Create an author, then a post referencing that author’s _id.
  3. Fetch the post with .populate('author'), and confirm the full author document appears nested inside, not just an ID.
  4. Fetch a post without .populate(), and confirm author is just a raw ObjectId. Explain, in your own words, what populate actually does.

Recap

  • Referencing stores another document’s _id, with ref: 'ModelName' telling Mongoose which model it points to, the MongoDB equivalent of a foreign key.
  • .populate(fieldName) loads the referenced document in place of the raw ID, MongoDB’s version of Prisma’s include.
  • Referencing fits data that’s shared, reused, or queried independently of its relationship, unlike the embedded comments from the last lesson.

Next lesson: choosing between embedding and referencing for a given relationship.