@nestbolt/likeable
Quick Start
Set up the module, decorate an entity, and like your first record in under five minutes.
This guide walks you through the minimal setup to start liking entities.
1. Register the Module
Add LikeableModule.forRoot() to your root module. The module is registered globally, so you only need to import it once:
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { LikeableModule } from "@nestbolt/likeable";
import { Post } from "./entities/post.entity";
@Module({
imports: [
TypeOrmModule.forRoot({
type: "postgres",
// ... your database config
autoLoadEntities: true,
synchronize: true,
}),
LikeableModule.forRoot(),
TypeOrmModule.forFeature([Post]),
],
})
export class AppModule {}The module registers LikeEntity and LikeableService globally. With autoLoadEntities: true, you don't need to add LikeEntity to the entities array yourself.
2. Decorate Your Entity
Add the @Likeable() decorator to your entity. To use the entity-level mixin methods (post.like(), post.toggle(), etc.), extend from LikeableMixin():
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";
import { Likeable, LikeableMixin } from "@nestbolt/likeable";
@Likeable()
@Entity("posts")
export class Post extends LikeableMixin(class { id!: string }) {
@PrimaryGeneratedColumn("uuid")
declare id: string;
@Column()
title!: string;
}No extra columns are needed on Post -- all like records live in the polymorphic likes table.
3. Like, Unlike, Toggle
That's it -- once the entity is loaded from the database, you can call the mixin methods directly:
@Injectable()
export class PostsService {
constructor(
@InjectRepository(Post) private readonly repo: Repository<Post>,
) {}
async like(postId: string, userId: string) {
const post = await this.repo.findOneByOrFail({ id: postId });
await post.like(userId);
return { count: await post.getLikesCount() };
}
async toggle(postId: string, userId: string) {
const post = await this.repo.findOneByOrFail({ id: postId });
const liked = await post.toggle(userId);
return { liked, count: await post.getLikesCount() };
}
}4. Use the Service Directly
If you don't want the mixin, inject LikeableService and pass the entity constructor + id:
import { LikeableService } from "@nestbolt/likeable";
@Injectable()
export class PostsService {
constructor(private readonly likeable: LikeableService) {}
like(postId: string, userId: string) {
return this.likeable.like(Post, postId, userId);
}
unlike(postId: string, userId: string) {
return this.likeable.unlike(Post, postId, userId);
}
isLikedBy(postId: string, userId: string) {
return this.likeable.isLikedBy(Post, postId, userId);
}
}5. Idempotent and Race-Safe
Calling like() twice for the same (user, entity) pair is a no-op -- the package relies on a unique index and silently swallows duplicate-insert errors. There's no need to check first:
await post.like(userId); // inserts row
await post.like(userId); // no-op, no exception
await post.like(userId); // still no-op
console.log(await post.getLikesCount()); // 1Concurrent duplicate likes from the same user resolve the same way -- one wins, the rest become no-ops.
6. Query a User's Likes
List every entity of a given type that a user has liked:
@Injectable()
export class PostsService {
constructor(
private readonly likeable: LikeableService,
@InjectRepository(Post) private readonly repo: Repository<Post>,
) {}
async getLikedPosts(userId: string) {
const ids = await this.likeable.getUserLikes(Post, userId);
return this.repo.findBy({ id: In(ids) });
}
}getUserLikes() returns the entity IDs ordered by most recently liked first.
Next Steps
- Configuration --
forRoot,forRootAsync, and the fullLikeableServiceAPI. - Decorator --
@Likeable()options reference. - Mixin -- entity-instance methods and composing with other mixins.
- Events -- listen to
like.likedandlike.unliked.