@nestbolt/likeable
Introduction
Polymorphic like/favorite/bookmark system for NestJS TypeORM entities with user-scoped uniqueness, race-safe inserts, and a fluent mixin API.
@nestbolt/likeable provides a polymorphic like/favorite/bookmark system for NestJS TypeORM entities. Decorate any entity with @Likeable() and users can like, unlike, toggle, and query likes through a typed service or directly on the entity instance via the mixin.
Key Features
- Polymorphic -- a single
likestable powers likes for every entity type. No per-entity join tables. - User-scoped uniqueness -- a unique index on
(user_id, likeable_type, likeable_id)guarantees one like per user per entity. - Idempotent and race-safe --
like()uses a direct insert with unique-violation handling, so concurrent duplicate likes resolve silently instead of throwing. - Toggle helper -- a single call flips like state and tells you the new state.
- Entity mixin --
LikeableMixinaddslike(),unlike(),toggle(),isLikedBy(),getLikesCount(), andgetLikers()directly on your entity instances. - Cross-entity queries -- list every entity of a given type that a user has liked, or count them.
- Event system -- react to
like.likedandlike.unlikedvia@nestjs/event-emitter(optional). - Custom polymorphic type -- override the type stored in the
likestable per entity via the@Likeable()decorator.
Architecture
The package is built around several core components:
LikeableModule registers all providers globally. You call LikeableModule.forRoot() or LikeableModule.forRootAsync() once in your root module, and LikeableService becomes available everywhere via dependency injection.
LikeableService is the primary API surface. It provides like(), unlike(), toggle(), isLikedBy(), getLikesCount(), getLikers(), getUserLikes(), and getUserLikesCount(). It owns the polymorphic type resolution and emits events when the optional @nestjs/event-emitter is registered.
LikeEntity is the single TypeORM entity backing the likes table. It stores likeable_type, likeable_id, user_id, and created_at, with a unique index on (user_id, likeable_type, likeable_id).
@Likeable() decorator stores configuration metadata on your entity class. The metadata is read by the service to resolve the polymorphic type stored in the likes table.
LikeableMixin is an optional mixin that adds convenience methods directly to your entity instances so you can call post.like(userId) instead of service.like(Post, post.id, userId).
Basic Usage
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";
import { Likeable, LikeableMixin } from "@nestbolt/likeable";
@Entity("posts")
@Likeable()
export class Post extends LikeableMixin(class { id!: string }) {
@PrimaryGeneratedColumn("uuid")
declare id: string;
@Column()
title!: string;
}
// Like, unlike, toggle, query
await post.like(userId);
await post.toggle(userId);
const count = await post.getLikesCount();
const isLiked = await post.isLikedBy(userId);Next Steps
- Installation -- install the package and its peer dependencies.
- Quick Start -- set up the module, decorate an entity, and like your first record in minutes.
- Configuration --
forRoot,forRootAsync, and the fullLikeableServiceAPI. - Decorator --
@Likeable()options reference. - Mixin -- add
like(),unlike(),toggle(), and friends directly to your entities. - Events -- listen to
like.likedandlike.unlikedevents.