@nestbolt/sluggable
Mixin
Add getSlug(), findBySlug(), and regenerateSlug() methods directly on your entity instances.
SluggableMixin adds convenience methods to your entity class so you can work with slugs directly on entity instances, without injecting SluggableService.
Basic Usage
Extend your entity from SluggableMixin():
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";
import { Sluggable, SluggableMixin } from "@nestbolt/sluggable";
@Sluggable({ from: "title", slugField: "permalink" })
@Entity("articles")
export class Article extends SluggableMixin(class {
id!: string;
title!: string;
permalink!: string;
}) {
@PrimaryGeneratedColumn("uuid")
declare id: string;
@Column()
declare title: string;
@Column({ default: "" })
declare permalink: string;
}The base class passed to SluggableMixin() declares the shape of your entity fields. Use declare in the extending class to redeclare the TypeORM-decorated columns.
Methods
getSlug()
Returns the current slug value from the entity:
const article = await repo.findOneBy({ id: "..." });
console.log(article.getSlug()); // "my-article-title"Uses the slugField from the @Sluggable() metadata. Defaults to "slug" if no metadata is found.
getSlugField()
Returns the name of the slug column:
const article = new Article();
console.log(article.getSlugField()); // "permalink"findBySlug(slug)
Finds an entity of the same type by slug:
const article = new Article();
const found = await article.findBySlug("my-article-title");
// Article | nullThis method requires SluggableModule to be initialized. If called before module initialization, it throws SluggableNotInitializedException.
regenerateSlug()
Regenerates the slug from the current source field values and updates the entity:
const article = await repo.findOneBy({ id: "..." });
article.title = "Updated Title";
const newSlug = await article.regenerateSlug();
console.log(newSlug); // "updated-title"
console.log(article.permalink); // "updated-title"The new slug is both returned and set on the entity. Uniqueness is enforced if unique: true in the decorator.
Composing with Other Mixins
SluggableMixin composes with other mixins like HasMediaMixin from @nestbolt/medialibrary:
import { HasMedia, HasMediaMixin } from "@nestbolt/medialibrary";
import { Sluggable, SluggableMixin } from "@nestbolt/sluggable";
@Sluggable({ from: "name" })
@HasMedia({ modelType: "Product" })
@Entity("products")
export class Product extends SluggableMixin(
HasMediaMixin(class {
id!: string;
}),
) {
@PrimaryGeneratedColumn("uuid")
declare id: string;
@Column()
name!: string;
@Column({ default: "" })
slug!: string;
}
// Product now has both media methods and slug methods:
// product.getSlug(), product.findBySlug(), product.regenerateSlug()
// product.addMedia(), product.getMedia(), product.getFirstMediaUrl()Without the Decorator
If you use SluggableMixin without the @Sluggable() decorator, the methods still work with defaults:
getSlug()reads from the"slug"fieldgetSlugField()returns"slug"regenerateSlug()returns""(no source fields to generate from)
Error Handling
If SluggableModule has not been initialized when you call findBySlug() or regenerateSlug(), a SluggableNotInitializedException is thrown:
import { SluggableNotInitializedException } from "@nestbolt/sluggable";
try {
await article.findBySlug("test");
} catch (error) {
if (error instanceof SluggableNotInitializedException) {
console.error("SluggableModule has not been initialized");
}
}