Mongoose populate made easy
Mongoose is an Object Data Mapper that wraps around MongoDB’s driver and helps manage relationships between data and provides schema validation out of the box. One core feature of databases is JOIN which combines rows from two or more tables, based on a related column between them. MongoDB has the join-like aggregation that combined to Mongoose lets you reference documents in other collections by reference IDs.
Mongoose has several features but the one we’re going to discuss today is populate as it could be quite confusing for starters. I believe you’re already familiar with creating an express app, so we won’t be covering setup and installation in this tutorial, instead, we’d look at a mongoose Schema and understand how population works once and for all.
We would create a One to One relationship where a Car would have an User(Owner), we would look at the steps involved in creating this relationship and how easy it is to achieve with mongoose populate method.
import mongoose, { Schema } from "mongoose";
const CarModel = new Schema({
brand: {
type: String,
enum: ["mercedes", "bmw", "toyota", "hyundai", "nissan"],
},
yearOfManufacture: Number,
mileage: Number,
condition: {
type: String,
enum: ["new", "used"],
},
owner: {
type: Schema.Types.ObjectId,
ref: "User",
},
});
const Car = mongoose.model("Car", CarModel);
export default Car;
import mongoose from "mongoose";
const { Schema, model } = mongoose;
// Validation: prevents unintentional omission of fields i.e required
const userSchema = new Schema({
email: {
type: String,
required: [true, "User must have an email address"],
},
password: {
type: String,
required: true,
},
});
const User = model("User", userSchema);
export default User;
Above, we have 2 models namely User and Car.
Population
Population seeks to link the User and Car models together using the join technique. To achieve this, we simply pass a User ref to the Car owner property and this grabs the _id under the hood and fleshes out its property. Populate passes down the _id to an internal function that filters through documents in the collection and returns the document(s) that match the user _id and returns the full object.
Steps:
1. Create a car using the Car model
import asyncHandler from "express-async-handler";
import User from "./../../models/User.js";
import Car from "./../../models/Car.js";
export const createCar = asyncHandler(async (req, res) => {
const { body } = req;
const newCar = await Car.create({ ...body });
res.status(201).json({
car: newCar,
status: 201,
});
});
This creates a new car in the car collection, look at the req.body, we intentionally omitted the owner, this is because there must be an available car in order to assign it an owner.
2. Assign a car to an owner
export const assignCarToOwner = async (req, res) => {
try {
const userId = req.query.userId;
const carId = req.query.carId;
const user = await User.findOne({ _id: userId });
const car = await Car.findOneAndUpdate(
{ _id: carId },
{ owner: user._id },
{ new: true },
);
res.json({ car });
} catch (error) {
console.log(error);
}
};
This assigns the owner a user based on the userId, notice the owner property simply has a string value of the userId, we would use populate to flesh out the owner property.
3. Populating the owner property
export const getCar = asyncHandler(async (req, res) => {
const car = await Car.find({ _id: req.params.id }).populate("owner");
res.json({ status: 201, car });
});
Finally, we have the complete User assigned to the owner property in the Car model.
Thanks for reading 👏🏾