NestboltNestbolt

@nestbolt/taggable

Tag CRUD

Create, find, and delete tags with names, slugs, types, and metadata.

Creating Tags

createTag(name, options?)

Creates a new tag. A URL-friendly slug is automatically generated from the name:

const tag = await taggableService.createTag("JavaScript");
// { id: "...", name: "JavaScript", slug: "javascript", type: null, metadata: null }

Options

OptionTypeDescription
slugstringCustom slug (overrides auto-generated)
typestringTag type/group for categorization
metadataanyArbitrary JSON metadata stored as a string
// With type
const tag = await taggableService.createTag("Frontend", { type: "category" });

// With metadata
const tag = await taggableService.createTag("VIP", {
  metadata: { color: "gold", priority: 1 },
});

// With custom slug
const tag = await taggableService.createTag("C++", { slug: "cpp" });

findOrCreateTag(name, options?)

Finds an existing tag by slug, or creates a new one if it doesn't exist. This is the recommended way to ensure tags exist before attaching them:

const tag = await taggableService.findOrCreateTag("TypeScript");
// Returns existing tag if "typescript" slug exists, otherwise creates it

When a type is provided, the lookup also filters by type:

const category = await taggableService.findOrCreateTag("Frontend", { type: "category" });
const label = await taggableService.findOrCreateTag("Frontend", { type: "label" });
// Two separate tags with the same name but different types

Finding Tags

findTagById(id)

const tag = await taggableService.findTagById("abc-123");
// TagEntity | null

findTagBySlug(slug, type?)

const tag = await taggableService.findTagBySlug("javascript");
// TagEntity | null

// With type filter
const tag = await taggableService.findTagBySlug("frontend", "category");

findTagsByType(type)

Returns all tags of a given type, sorted alphabetically:

const categories = await taggableService.findTagsByType("category");
// [{ name: "Backend", ... }, { name: "Frontend", ... }]

getAllTags()

Returns all tags, sorted alphabetically by name:

const all = await taggableService.getAllTags();

Deleting Tags

deleteTag(id)

Deletes a tag and all its pivot records (attachments). If the tag doesn't exist, the call is a no-op:

await taggableService.deleteTag(tagId);

This removes the tag from all entities it was attached to.

Tag Entity

The tags table has the following structure:

ColumnTypeDescription
idUUIDPrimary key
namevarchar(255)Tag display name
slugvarchar(255)URL-friendly slug (auto-generated)
typevarchar(100)Optional type/group (nullable)
metadatatextOptional JSON metadata (nullable)
created_attimestampCreation timestamp
updated_attimestampLast update timestamp

Unique Constraint

Tags have a composite unique index on [slug, type]. This means:

  • You can have one tag with slug "javascript" and type null
  • You can have another with slug "javascript" and type "language"
  • But you cannot have two tags with the same slug and type combination

Slug Generation

Tag names are automatically converted to URL-friendly slugs:

  • Lowercased
  • Spaces replaced with hyphens
  • Special characters removed
  • Leading/trailing hyphens trimmed
"JavaScript"     → "javascript"
"C#"             → "c"
"Node.js"        → "nodejs"
"Machine Learning" → "machine-learning"

Metadata

Metadata is stored as a JSON string. When creating a tag, pass any serializable value:

const tag = await taggableService.createTag("Featured", {
  metadata: { color: "#ff0000", icon: "star" },
});

// Reading metadata
const meta = JSON.parse(tag.metadata!);
console.log(meta.color); // "#ff0000"