npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

mongoose-reactions

v0.9.3

Published

TypeScript-friendly Mongoose plugin that adds polymorphic reaction support (likes, loves, haha, custom) to any Mongoose model.

Readme

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.


Table of Contents


Features

| ✅ | 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. |


Installation

# Using npm
npm install mongoose-reactions

# Using yarn
yarn add mongoose-reactions

# Using pnpm
pnpm add mongoose-reactions

Peer dependency: mongoose@^8.18.0. Make sure Mongoose is installed in your project.


Quick Start

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);
}

Configuration Options

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,
});

Static API

| 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.


Instance API

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');

Advanced Usage

Transactions

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();
}

Custom Reaction Model Name

If you need a differently named collection:

postSchema.plugin(reactionsPlugin, { reactionModelName: 'PostReaction' });

The collection will be postreactions (Mongoose pluralizes automatically).

Adding Extra Fields

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).


Contributing

  1. Fork the repository.
  2. Create a branch for your feature or bugfix.
  3. Write tests for any new functionality.
  4. Run lint & format: npm run lint and npm run prelint.
  5. Commit using Conventional Commits (npm run commit).
  6. 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.


License

MIT © 2025 Ali Nazari

See the full license text in the LICENSE file.


Contact

If you encounter bugs or have feature ideas, feel free to open an issue or contact the maintainer at [email protected].


Happy reacting!