Mongoose‑Reactions is a TypeScript‑first Mongoose plugin that adds polymorphic reaction support (like 👍, ❤️, 😂, or any custom emoji) to any Mongoose model.
It works with both single‑reaction‑per‑user and multi‑reaction‑per‑user modes, offers a full‑featured static and instance API, and is fully typed.
- Features
- Installation
- Quick Start
- Configuration Options
- Static API
- Instance API
- Advanced Usage
- Contributing
- License
| ✅ | Feature |
|---|---|
| ✅ | Polymorphic reactions – attach reactions to any model (posts, comments, users, etc.). |
| ✅ | Single‑ or multi‑reaction per user – choose whether a user can have only one reaction per reactable or many different reactions. |
| ✅ | Whitelist & case‑insensitivity – restrict allowed reaction types and optionally treat them case‑insensitively. |
| ✅ | Typed API – full TypeScript definitions for all static and instance methods. |
| ✅ | Atomic upserts – uses MongoDB unique indexes to guarantee consistency under concurrency. |
| ✅ | Aggregation helpers – quickly get reaction counts per type. |
| ✅ | Session support – all operations accept an optional ClientSession for transaction safety. |
| ✅ | Custom metadata – store arbitrary extra data (meta) with each reaction. |
# Using npm
npm install mongoose-reactions
# Using yarn
yarn add mongoose-reactions
# Using pnpm
pnpm add mongoose-reactionsPeer dependency:
mongoose@^8.18.0. Make sure Mongoose is installed in your project.
import mongoose from 'mongoose';
import { reactionsPlugin } from 'mongoose-reactions';
// Define a simple Post schema
const postSchema = new mongoose.Schema({
title: String,
content: String,
});
// Apply the plugin (default options)
postSchema.plugin(reactionsPlugin);
const Post = mongoose.model('Post', postSchema);
// --- Using the static API ---
async function demo() {
const post = await Post.create({ title: 'Hello', content: 'World' });
// User 1 likes the post
await Post.react(post._id, '60c72b2f9f1b2c001c8d4e9a', 'like');
// User 2 loves the post with extra meta
await Post.react(post._id, '60c72b2f9f1b2c001c8d4e9b', 'love', { source: 'mobile' });
// Toggle a reaction (remove if exists, add otherwise)
await Post.toggleReaction(post._id, '60c72b2f9f1b2c001c8d4e9a', 'like');
// Get counts
const counts = await Post.getReactionCounts(post._id);
console.log(counts); // { like: 0, love: 1 }
// List all reactors for a specific reaction
const lovers = await Post.listReactors(post._id, { reaction: 'love' });
console.log(lovers);
}When applying the plugin you can pass a PluginOptions object:
| Option | Type | Default | Description |
|---|---|---|---|
allowMultipleReactionsPerUser |
boolean |
false |
If true, a user may store many different reactions on the same reactable. |
reactionTypes |
string[] |
undefined |
Whitelist of allowed reaction identifiers (e.g. ['like', 'love', 'haha']). |
reactionTypesCaseInsensitive |
boolean |
true |
Convert incoming reaction strings to lower‑case before validation. |
reactionModelName |
string |
'Reaction' |
Name of the internal Mongoose model that stores reactions. |
// Example: enable multiple reactions and whitelist
postSchema.plugin(reactionsPlugin, {
allowMultipleReactionsPerUser: true,
reactionTypes: ['like', 'love', 'haha', 'wow'],
reactionTypesCaseInsensitive: false,
});| Method | Signature | Description |
|---|---|---|
react |
react(reactableId, userId, reaction, meta?, opOpts?) |
Add a reaction (or update it in single‑mode). Returns the created/updated document. |
unreact |
unreact(reactableId, userId, reaction?, opOpts?) |
Remove a specific reaction (if provided) or all reactions of a user on the reactable. Returns number of deleted docs. |
toggleReaction |
toggleReaction(reactableId, userId, reaction, meta?, opOpts?) |
Flip the presence of a reaction. Returns {removed:true} or the created/updated document. |
getReactionCounts |
getReactionCounts(reactableId, opOpts?) |
Returns an object mapping reaction types → counts. |
getUserReactions |
getUserReactions(reactableId, userId, options?) |
Fetch reactions a user has made on a reactable (array of docs). |
listReactors |
listReactors(reactableId, opts?) |
Paginated list of all reaction documents for a reactable, optionally filtered by reaction type. |
All static methods accept an optional opOpts object with a session field to run inside a MongoDB transaction.
When the plugin is applied, each document gains the following methods:
| Method | Signature | Description |
|---|---|---|
react |
(userId, reaction, meta?) |
Shortcut to the static react using the document’s _id. |
unreact |
(userId, reaction?) |
Shortcut to the static unreact for this document. |
Example:
const post = await Post.findById(id);
await post.react('60c72b2f9f1b2c001c8d4e9a', 'like');
await post.unreact('60c72b2f9f1b2c001c8d4e9a');const session = await mongoose.startSession();
session.startTransaction();
try {
await Post.react(postId, userId, 'like', undefined, { session });
await SomeOtherModel.updateOne(..., { session });
await session.commitTransaction();
} catch (e) {
await session.abortTransaction();
throw e;
} finally {
session.endSession();
}If you need a differently named collection:
postSchema.plugin(reactionsPlugin, { reactionModelName: 'PostReaction' });The collection will be postreactions (Mongoose pluralizes automatically).
You can extend the internal reaction schema via Mongoose discriminators or by creating a separate model that references the generated one. The plugin stores only the core fields (reactableId, reactableModel, user, reaction, meta, timestamps).
- Fork the repository.
- Create a branch for your feature or bugfix.
- Write tests for any new functionality.
- Run lint & format:
npm run lintandnpm run prelint. - Commit using Conventional Commits (
npm run commit). - Open a Pull Request – the CI will run linting, tests, and coverage checks automatically.
Please read the full CONTRIBUTING.md for guidelines on coding style, commit messages, and issue reporting.
MIT © 2025 Ali Nazari
See the full license text in the LICENSE file.
If you encounter bugs or have feature ideas, feel free to open an issue or contact the maintainer at backendwithali@gmail.com.
Happy reacting!
Built with ❤️ by Ali Nazari, for developers.