NestboltNestbolt

@nestbolt/taggable

Quick Start

Register the module, decorate an entity, and start tagging in under five minutes.

This guide walks you through the minimal setup to start tagging entities.

1. Register the Module

Add TaggableModule.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 { TaggableModule } from "@nestbolt/taggable";
import { Post } from "./entities/post.entity";

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: "postgres",
      // ... your database config
      entities: [Post],
      synchronize: true,
    }),
    TaggableModule.forRoot(),
  ],
})
export class AppModule {}

2. Mark Your Entity as Taggable

Add the @Taggable() decorator to your entity. Optionally extend TaggableMixin for convenience methods:

import { Entity, PrimaryGeneratedColumn, Column, BaseEntity } from "typeorm";
import { Taggable, TaggableMixin } from "@nestbolt/taggable";

@Taggable()
@Entity("posts")
export class Post extends TaggableMixin(BaseEntity) {
  @PrimaryGeneratedColumn("uuid")
  id!: string;

  @Column()
  title!: string;
}

3. Create and Attach Tags

Inject TaggableService and start tagging:

import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { TaggableService } from "@nestbolt/taggable";
import { Post } from "./entities/post.entity";

@Injectable()
export class PostService {
  constructor(
    @InjectRepository(Post) private readonly postRepo: Repository<Post>,
    private readonly taggableService: TaggableService,
  ) {}

  async createAndTag(title: string, tagNames: string[]) {
    const post = await this.postRepo.save(this.postRepo.create({ title }));

    for (const name of tagNames) {
      const tag = await this.taggableService.findOrCreateTag(name);
      await this.taggableService.attachTag(Post, post.id, tag.id);
    }

    return post;
  }
}

4. Query Tags on an Entity

// Get all tags for a post
const tags = await this.taggableService.getEntityTags(Post, postId);
console.log(tags.map(t => t.name)); // ["NestJS", "TypeORM"]

// Check if a post has a specific tag
const hasTag = await this.taggableService.hasTag(Post, postId, tag.id);

// Count tags
const count = await this.taggableService.getTagCount(Post, postId);

5. Use the Mixin Methods

If your entity extends TaggableMixin, you can call tag methods directly on entity instances:

const post = await this.postRepo.findOneBy({ id: postId });

await post.attachTag(tag.id);
await post.detachTag(tag.id);
await post.syncTags([tagId1, tagId2]);

const tags = await post.getTags();
const hasTag = await post.hasTag(tagId);
const count = await post.getTagCount();

6. Sync Tags

Replace all tags on an entity with a new set -- missing tags are attached, extra tags are removed:

await this.taggableService.syncTags(Post, postId, [tagId1, tagId2, tagId3]);

What Happened Behind the Scenes

  1. TaggableModule.forRoot() registered TaggableService globally and created the tags and taggables tables via TypeORM.
  2. The @Taggable() decorator stored metadata on the Post class, mapping it to the type "Post" in the pivot table.
  3. findOrCreateTag() looked up existing tags by slug and created new ones as needed.
  4. attachTag() created a row in the taggables pivot table linking the tag to the post.
  5. Query methods read from the pivot table with the correct taggableType filter, so tags for different entity types never mix.

Next Steps

  • Configuration -- forRoot and forRootAsync module setup.
  • Decorator -- customize the entity type name.
  • Mixin -- all mixin methods explained.
  • Tag CRUD -- create tags with types, metadata, and custom slugs.
  • Events -- listen to tag lifecycle events.