Skip to content

Instantly share code, notes, and snippets.

@jonshipman
Created October 31, 2022 20:33
Show Gist options
  • Save jonshipman/b61526c9abd0897ddca807091b43280f to your computer and use it in GitHub Desktop.
Save jonshipman/b61526c9abd0897ddca807091b43280f to your computer and use it in GitHub Desktop.
A no-plugins approach to implementing virtuals that use a promise.

Mongoose Async Virtuals

Demo of Async Virtuals in Monoose 6.2.7. Using no plugins, it's three steps.

  1. Declare an instance method (it's loadChildren in the example). The instance method should store the results into a this._variable (variable name is not relevant, just be sure not to use one that's already in use).

  2. In schema.post('find'), run an Promise that fires your instance method in a loop using await Promise.all().

  3. Use schema.virtual('variable') to declare a virtual type and return the populated "private" variable you declared in step one.

Adding virtuals: true to your toObject and toJSON Schema options will cause your virtual to auto return when implementing.

  • Note: the way this is setup, document.loadChildren will always fire. If this is unwanted, you could create a schema.pre('find') that will look at the passed options and conditionally loadChildren in the post('find') middleware.
import mongoose from 'mongoose';
const { Schema } = mongoose;

class Comment extends Schema.Model {
	async loadChildren() {
		if (!this._id) throw new Error('No _id');
		if (this._children) return this._children;

		this._children = await mongoose.model('Comment').find({ parent: this._id });

		return this._children;
	}
}

const commentSchema = new Schema(
	{
		created: Date,
		message: String,
		modified: Date,
		parent: Schema.Types.ObjectId,
		title: String,
	},
	{
		collection: 'comment',
		timestamps: { createdAt: 'created', updatedAt: 'modified' },
		toJSON: { virtuals: true },
		toObject: { virtuals: true },
	}
);

commentSchema.post('find', async function (d) {
	const ops = [];
	for (const document of d) {
		if (document.loadChildren) {
			ops.push(document.loadChildren());
		}
	}

	await Promise.all(ops);
});

commentSchema.virtual('children').get(function () {
	return this._children;
});

commentSchema.loadClass(Comment);

const CommentModel = new model('Comment', commentSchema);
export { CommentModel };

A similar approach can be used for findOne.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment