@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
| Option | Type | Description |
|---|---|---|
slug | string | Custom slug (overrides auto-generated) |
type | string | Tag type/group for categorization |
metadata | any | Arbitrary 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 itWhen 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 typesFinding Tags
findTagById(id)
const tag = await taggableService.findTagById("abc-123");
// TagEntity | nullfindTagBySlug(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:
| Column | Type | Description |
|---|---|---|
id | UUID | Primary key |
name | varchar(255) | Tag display name |
slug | varchar(255) | URL-friendly slug (auto-generated) |
type | varchar(100) | Optional type/group (nullable) |
metadata | text | Optional JSON metadata (nullable) |
created_at | timestamp | Creation timestamp |
updated_at | timestamp | Last update timestamp |
Unique Constraint
Tags have a composite unique index on [slug, type]. This means:
- You can have one tag with slug
"javascript"and typenull - 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"