NestboltNestbolt

@nestbolt/taggable

Entity Mixin

TaggableMixin adds tag methods directly to your entity instances.

TaggableMixin

TaggableMixin is an optional mixin that adds convenience methods directly to your entity instances. Instead of calling TaggableService with entity class and ID arguments, you call methods directly on the entity:

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

Methods

MethodReturnsDescription
attachTag(tagId)Promise<void>Attach a tag to this entity
detachTag(tagId)Promise<void>Detach a tag from this entity
syncTags(tagIds)Promise<void>Sync tags -- adds missing, removes extra
getTags()Promise<TagEntity[]>Get all tags on this entity
hasTag(tagId)Promise<boolean>Check if this entity has a specific tag
getTagCount()Promise<number>Count tags on this entity
getTaggableType()stringGet the entity type name
getTaggableId()stringGet the entity ID

Usage Examples

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

// Attach and detach
await post.attachTag(tagId);
await post.detachTag(tagId);

// Sync -- replaces all tags with the given set
await post.syncTags([tagId1, tagId2, tagId3]);

// Query
const tags = await post.getTags();
const hasNestJS = await post.hasTag(nestjsTagId);
const count = await post.getTagCount();

// Type info
console.log(post.getTaggableType()); // "Post"
console.log(post.getTaggableId());   // "abc-123-..."

Composing with Other Mixins

TaggableMixin can be composed with other mixins by nesting them:

import { TaggableMixin } from "@nestbolt/taggable";
import { SluggableMixin } from "@nestbolt/sluggable";
import { HasMediaMixin } from "@nestbolt/medialibrary";

@Taggable()
@Sluggable({ from: "title" })
@Entity("posts")
export class Post extends TaggableMixin(SluggableMixin(HasMediaMixin(BaseEntity))) {
  @PrimaryGeneratedColumn("uuid")
  id!: string;

  @Column()
  title!: string;

  @Column({ default: "" })
  slug!: string;
}

The order of nesting doesn't matter -- each mixin adds its own methods independently.

Error Handling

If you call a mixin method before TaggableModule has been initialized, a TaggableNotInitializedException is thrown:

import { TaggableNotInitializedException } from "@nestbolt/taggable";

try {
  await post.attachTag(tagId);
} catch (error) {
  if (error instanceof TaggableNotInitializedException) {
    console.log(error.message);
    // "TaggableModule has not been initialized. Make sure TaggableModule.forRoot() is imported."
  }
}

This typically happens when:

  • TaggableModule.forRoot() was not imported in your root module
  • You're calling mixin methods outside the NestJS application lifecycle (e.g., in a standalone script without bootstrapping the module)

Without the Mixin

The mixin is entirely optional. You can use TaggableService directly for all operations:

// With mixin
await post.attachTag(tagId);

// Without mixin (equivalent)
await taggableService.attachTag(Post, post.id, tagId);

The mixin simply delegates to TaggableService internally, using the entity's constructor and ID.