2.2.1 • Published 2 months ago

@adonisjs/lucid-slugify v2.2.1

Weekly downloads
593
License
MIT
Repository
github
Last release
2 months ago

Lucid Slugify


gh-workflow-image npm-image license-image typescript-image

Generating slugs is easy, but keeping them unique and within a maximum length range is hard. This package abstracts the hard parts and gives you a simple API to generate unique slugs.

Features

  • Define a maximum length for the slug
  • Complete words when truncating the slug
  • Generate unique slugs using different strategies
  • Add your custom strategies

Usage

Install and configure the package as follows:

node ace add @adonisjs/lucid-slugify

Once done, you need to use the following decorator on the field for which you want to generate the slug. Following is an example with the Post model generating slug from the post title.

import { BaseModel, column } from '@adonisjs/lucid/orm'
import slugify from '@adonisjs/lucid-slugify/decorator'

class Post extends BaseModel {
  @column({ isPrimary: true })
  public id: number

  @column()
  @slugify({
    strategy: 'dbIncrement',
    fields: ['title']
  })
  public slug: string

  @column()
  public title: string
}

In the above example, the slug property will be set based upon the value of the title property.

Updating slugs

By default, slugs are not updated when you update a model instance, and this is how it should be when slugs are used to look up a record, as changing a slug will result in a broken URL.

However, if slugs are not primarily used to look up records, you may want to update them.

You can enable updates by using the allowUpdates flag.

@slugify({
  strategy: 'dbIncrement',
  fields: ['title'],
  allowUpdates: true,
})
public slug: string

Generate slug from multiple properties

The fields array can accept multiple model properties and generate a slug by concatenating the values of all the fields.

@slugify({
  strategy: 'dbIncrement',
  fields: ['country', 'state', 'city'],
  allowUpdates: true,
})
public location: string

Null values and slug generation

The slugify decorator does not generate slugs when the source field(s) value is not defined or null.

In other words, all of the source fields should have a value for the slug to be generated. It is an opinionated choice and not likely to change.

Available options

Following is the list of available options accepted by the @slugify decorator.

Strategies

Strategies decide how to generate a slug and then make it unique. This package ships with three different strategies.

  • simple: Just the slug is generated. No uniqueness guaranteed.
  • dbIncrement: Generates unique slugs by adding a counter to the existing similar slug.
  • shortId: Appends a short id to the initial slug value to ensure uniqueness.

Db Increment

The Db Increment strategy uses a counter to generate unique slugs. Given the following table structure and data.

+----+-------------+-------------+
| id | title       | slug        |
+----+-------------+-------------+
| 1  | Hello world | hello-world |
+----+-------------+-------------+

If you generate another slug for the Hello world title, the dbIncrement strategy will append -1 to ensure slug uniqueness.

Model definition

import { BaseModel, column } from '@adonisjs/lucid/orm'
import slugify from '@adonisjs/lucid-slugify/decorator'

class Post extends BaseModel {
  @column({ isPrimary: true })
  public id: number

  @column()
  @slugify({
    strategy: 'dbIncrement',
    fields: ['title']
  })
  public slug: string

  @column()
  public title: string
}

Create a new record

const post = new Post()
post.title = 'Hello world'

await post.save()

Database state

+----+-------------+---------------+
| id | title       | slug          |
+----+-------------+---------------+
| 1  | Hello world | hello-world   |
| 2  | Hello world | hello-world-1 |
+----+-------------+---------------+

Implementation details

The implementation details vary a lot across different database drivers.

  • PostgreSQL, MsSQL 8.0, and Redshift performs optimized queries to fetch only matching record with the largest counter.
  • For SQLite, MySQL < 8.0, and MSSQL, we have to fetch all the matching rows and then find the largest counter in JavaScript.
  • The OracleDB implementation is untested (feel free to contribute the tests). However, it also performs an optimized query to fetch only matching records with the largest counter.

Simple

The simple strategy just generates a slug respecting the maxLength and completeWords config options. No uniqueness is guaranteed when using this strategy.

import { BaseModel, column } from '@adonisjs/lucid/orm'
import slugify from '@adonisjs/lucid-slugify/decorator'

class Post extends BaseModel {
  @column({ isPrimary: true })
  public id: number

  @column()
  @slugify({
    strategy: 'simple',
    fields: ['title']
  })
  public slug: string

  @column()
  public title: string
}

Short Id

The shortId strategy appends a ten-digit long random short id to the initial slug value for uniqueness. Following is an example of using the shortId strategy.

import { BaseModel, column } from '@adonisjs/lucid/orm'
import slugify from '@adonisjs/lucid-slugify/decorator'

class Post extends BaseModel {
  @column({ isPrimary: true })
  public id: number

  @column()
  @slugify({
    strategy: 'shortId',
    fields: ['title']
  })
  public slug: string

  @column()
  public title: string
}
+----+-------------+------------------------+
| id | title       | slug                   |
+----+-------------+------------------------+
| 1  | Hello world | hello-world-yRPZZIWGgC |
+----+-------------+------------------------+

Adding a custom strategy

You can add custom strategies using two different ways.

Inline within the slugify decorator

The simplest way is to define the strategy inline in the decorator options. A strategy must implement the following two methods.

import { SlugifyStrategy } from '@adonisjs/lucid-slugify/types

const myCustomStrategy: SlugifyStrategy = {
  makeSlug (model, field, value) {
    return // slug for the value
  },
  makeSlugUnique(model, field, slug) {
    return // make slug unique
  },
}

@slugify({
  strategy: myCustomStrategy,
  fields: ['title']
})

Extending the slugify package

This is the recommended approach when you are distributing your strategy as an npm package. Every strategy must implement the SlugifyStrategy interface.

Define strategy

import {
  SlugifyConfig,
  SlugifyStrategy
} from '@adonisjs/lucid-slugify/types'

class MyStrategy implements SlugifyStrategy {
  constructor (private config: SlugifyConfig) {}

  makeSlug (
    model: LucidModel,
    field: string,
    value: string
  ) {}

  makeSlugUnique (
    model: LucidModel,
    field: string,
    slug: string
  ) {}
}

Register the strategy

Register the strategy using the slugify.extend method. You must write the following code inside the provider boot method.

import type { ApplicationService } from '@adonisjs/core/types'

export default class AppProvider {
  constructor(protected app: ApplicationService) {}

  public async boot() {
    const slugify = await this.app.container.make('slugify.manager')

    slugify.extend('strategyName', (slugify, config) => {
      return new MyStrategy(config)
    })
  }
}

Inform typescript about the strategy

Finally, you will also have to inform typescript about the new strategy you added using the slugify.extend method. We will use declaration merging to add the property to the StrategiesList interface.

declare module '@adonisjs/lucid-slugify/types' {
  interface StrategiesList {
    strategyName: SlugifyStrategy
  }
}
3.0.0-0

2 months ago

2.2.1

2 years ago

2.2.0

2 years ago

2.1.5

2 years ago

2.1.4

2 years ago

2.1.3

3 years ago

2.1.2

3 years ago

2.1.1

3 years ago

2.1.0

3 years ago

2.0.1

3 years ago

2.0.0

3 years ago

1.0.3

6 years ago

1.0.2

6 years ago

1.0.1

6 years ago

1.0.0

6 years ago