Disclaimer: I am not certain if the issue is caused by TypeScript, but I believe it might be, as my populate functionality worked correctly in previous JavaScript projects.
I’m working on a project using Node.js and Express with TypeScript for the backend. The application is a Learning Management System (LMS), and MongoDB is used to manage the data. In my schema definitions, certain fields are stored as ObjectIds, and I’m looking for a way to populate these fields when fetching batch details. These fields can either be a single ObjectId or an array of ObjectIds, and they are reflected accordingly in the TypeScript type definitions for the schema.
The issue here is that the returned data is not populated.
My current code looks like:
export const getBatch = async (req: Request, res: Response) => {
const { id } = req.params;
const user = req.user!;
try {
const batchQuery = Batch.findById(id);
const userInfoSelectString = "firstName lastName email countryCode contact accountStatus";
if (user.role === UserRole.USER) {
batchQuery.where("learnerIds").in(user.id);
batchQuery.select("-learnerIds -createdBy -updatedBy");
} else if (user.role === UserRole.TRAINER) {
batchQuery.where("learnerIds").equals(user.id);
}
if (user.role !== UserRole.USER) {
batchQuery
.populate({ path: "learnerIds", select: userInfoSelectString })
.populate({ path: "createdBy", select: userInfoSelectString })
.populate({ path: "updatedBy", select: userInfoSelectString });
}
batchQuery.populate({ path: "trainerId", select: userInfoSelectString });
const batch = await batchQuery;
res.status(StatusCodes.OK).json({ success: true, message: "Batch fetched successful!", data: batch });
} catch (error) {
throw new CustomAPIError("Failed to fetch batch!", StatusCodes.INTERNAL_SERVER_ERROR);
}
};
Type definition
export interface IBatch extends Document {
name: string;
startingDate: Date;
numberOfLearners: number;
trainerId: ObjectId | IUser | null;
learnerIds: (ObjectId | IUser)[];
status: BatchStatus;
createdBy: ObjectId | IUser;
updatedBy: ObjectId | IUser;
}
Mongoose Model Schema
import mongoose from "mongoose";
import { IBatch } from "../types/db";
import { BatchStatus } from "../lib/enums";
const BatchSchema = new mongoose.Schema<IBatch>(
{
name: {
type: String,
required: [true, "Please provide email!"],
},
startingDate: {
type: Date,
required: [true, "Please provide starting date!"],
},
numberOfLearners: {
type: Number,
default: 0,
},
trainerId: {
type: mongoose.Schema.Types.ObjectId,
default: null,
},
learnerIds: {
type: [mongoose.Schema.Types.ObjectId],
default: [],
},
status: {
type: String,
default: BatchStatus.UPCOMING,
enum: {
values: ["UPCOMING", "ONGOING", "DISABLED", "COMPLETED"],
message: "{VALUE} is not supported",
},
},
createdBy: {
type: mongoose.Schema.Types.ObjectId,
required: [true, "Please specify who created this batch!"],
},
updatedBy: {
type: mongoose.Schema.Types.ObjectId,
required: [true, "Please specify who updated this batch!"],
},
},
{ timestamps: true },
);
export const Batch = mongoose.model<IBatch>("Batch", BatchSchema);
One of the many variations I tried was but it did not work
if (user.role !== UserRole.USER) {
batchQuery
.populate<{ learnerIds: IUser[] }>({ path: "learnerIds", select: userInfoSelectString })
.populate<{ createdBy: IUser }>({ path: "createdBy", select: userInfoSelectString })
.populate<{ updatedBy: IUser }>({ path: "updatedBy", select: userInfoSelectString });
}
batchQuery.populate<{ learnerIds: IUser }>({ path: "trainerId", select: userInfoSelectString });