Skip to content

Repository files navigation

@nestbolt/likeable

Polymorphic like/favorite/bookmark system for NestJS with TypeORM — like any entity.

npm versionnpm downloadstestslicense


This package provides a polymorphic like/favorite system for NestJS that lets users like, favorite, or bookmark any entity with user-scoped uniqueness and like counts.

Once installed, using it is as simple as:

@Entity("posts")
@Likeable()exportclassPostextendsLikeableMixin(BaseEntity){
@PrimaryGeneratedColumn("uuid")id!: string;
@Column()title!: string;}// Like, unlike, toggleawaitlikeableService.like(Post,postId,userId);constcount=awaitlikeableService.getLikesCount(Post,postId);

Table of Contents

Installation

Install the package via npm:

npm install @nestbolt/likeable

Or via yarn:

yarn add @nestbolt/likeable

Or via pnpm:

pnpm add @nestbolt/likeable

Peer Dependencies

This package requires the following peer dependencies, which you likely already have in a NestJS project:

@nestjs/common ^10.0.0 || ^11.0.0
@nestjs/core ^10.0.0 || ^11.0.0
@nestjs/typeorm ^10.0.0 || ^11.0.0
typeorm ^0.3.0
reflect-metadata ^0.1.13 || ^0.2.0

Optional

npm install @nestjs/event-emitter # For like.liked, like.unliked events

Quick Start

1. Register the module in your AppModule

import{LikeableModule}from"@nestbolt/likeable";
@Module({imports: [TypeOrmModule.forRoot({/* ... */}),LikeableModule.forRoot(),],})exportclassAppModule{}

2. Mark entities as likeable

import{Likeable,LikeableMixin}from"@nestbolt/likeable";
@Entity("posts")
@Likeable()exportclassPostextendsLikeableMixin(BaseEntity){
@PrimaryGeneratedColumn("uuid")id!: string;
@Column()title!: string;}

3. Use the service to like entities

import{LikeableService}from"@nestbolt/likeable";
@Injectable()exportclassPostService{constructor(privatereadonlylikeableService: LikeableService){}asynclikePost(postId: string,userId: string){awaitthis.likeableService.like(Post,postId,userId);}}

Module Configuration

The module is registered globally — you only need to import it once.

Static Configuration (forRoot)

LikeableModule.forRoot();

Async Configuration (forRootAsync)

LikeableModule.forRootAsync({imports: [ConfigModule],inject: [ConfigService],useFactory: (config: ConfigService)=>({}),});

Using the @Likeable() Decorator

The @Likeable() class decorator marks an entity for polymorphic liking:

@Likeable()// type defaults to class name
@Likeable({type: "BlogPost"})// custom type override
OptionTypeDefaultDescription
typestringClass nameOverride the entity type name in likes table

Like Operations

// Like an entity (idempotent — won't duplicate)awaitlikeableService.like(Post,postId,userId);// Unlike an entityawaitlikeableService.unlike(Post,postId,userId);// Toggle — returns true if liked, false if unlikedconstisNowLiked=awaitlikeableService.toggle(Post,postId,userId);

Query Methods

// Check if a user liked an entityconstliked=awaitlikeableService.isLikedBy(Post,postId,userId);// Get total likes count for an entityconstcount=awaitlikeableService.getLikesCount(Post,postId);// Get user IDs who liked an entityconstlikerIds=awaitlikeableService.getLikers(Post,postId);// Get entity IDs liked by a userconstlikedPostIds=awaitlikeableService.getUserLikes(Post,userId);// Get count of entities liked by a userconstuserLikesCount=awaitlikeableService.getUserLikesCount(Post,userId);

Entity Mixin

The LikeableMixin adds convenience methods directly on your entity:

@Entity("posts")
@Likeable()exportclassPostextendsLikeableMixin(BaseEntity){// ...}// Usageconstpost=awaitpostRepo.findOneBy({ id });awaitpost.like(userId);awaitpost.unlike(userId);constisNowLiked=awaitpost.toggle(userId);constliked=awaitpost.isLikedBy(userId);constcount=awaitpost.getLikesCount();constlikers=awaitpost.getLikers();
MethodReturnsDescription
like(userId)Promise<void>Like this entity
unlike(userId)Promise<void>Unlike this entity
toggle(userId)Promise<boolean>Toggle like status
isLikedBy(userId)Promise<boolean>Check if user liked entity
getLikesCount()Promise<number>Get total likes count
getLikers()Promise<string[]>Get user IDs of likers

Events

When @nestjs/event-emitter is installed, the package emits:

EventPayloadWhen
like.liked{ likeableType, likeableId, userId }After an entity is liked
like.unliked{ likeableType, likeableId, userId }After an entity is unliked
import{LIKEABLE_EVENTS,LikedEvent}from"@nestbolt/likeable";import{OnEvent}from"@nestjs/event-emitter";
@OnEvent(LIKEABLE_EVENTS.LIKED)handleLiked(event: LikedEvent){console.log(`${event.likeableType}#${event.likeableId} liked by ${event.userId}`);}

Using the Service Directly

Inject LikeableService for like management and querying:

import{LikeableService}from"@nestbolt/likeable";
@Injectable()exportclassPostService{constructor(privatereadonlylikeableService: LikeableService){}asyncgetPostWithLikeStatus(postId: string,userId: string){constpost=awaitthis.postRepo.findOneBy({id: postId});constisLiked=awaitthis.likeableService.isLikedBy(Post,postId,userId);constlikesCount=awaitthis.likeableService.getLikesCount(Post,postId);return{ ...post, isLiked, likesCount };}}
MethodReturnsDescription
like(Entity, entityId, userId)Promise<void>Like an entity
unlike(Entity, entityId, userId)Promise<void>Unlike an entity
toggle(Entity, entityId, userId)Promise<boolean>Toggle like status
isLikedBy(Entity, entityId, userId)Promise<boolean>Check if user liked entity
getLikesCount(Entity, entityId)Promise<number>Get total likes count
getLikers(Entity, entityId)Promise<string[]>Get user IDs of likers
getUserLikes(Entity, userId)Promise<string[]>Get entity IDs liked by user
getUserLikesCount(Entity, userId)Promise<number>Count entities liked by user
isLikeable(Entity)booleanCheck for @Likeable metadata

Like Entity

The likes table stores:

ColumnTypeDescription
idUUIDPrimary key
likeable_typevarchar(255)Entity type name
likeable_idvarchar(36)Entity ID
user_idvarchar(36)User who liked
created_attimestampWhen the like was created

A unique index on (user_id, likeable_type, likeable_id) ensures one like per user per entity.

Testing

npm test

Run tests in watch mode:

npm run test:watch

Generate coverage report:

npm run test:cov

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security

If you discover any security-related issues, please report them via GitHub Issues with the security label instead of using the public issue tracker.

Credits

License

The MIT License (MIT). Please see License File for more information.

About

Polymorphic like/favorite/bookmark system for NestJS with TypeORM — like any entity.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages