@adonisjs/lucid-slugify

Slugify addon for Adonisjs Lucid ORM

Usage no npm install needed!

<script type="module">
  import adonisjsLucidSlugify from 'https://cdn.skypack.dev/@adonisjs/lucid-slugify';
</script>

README

Adonis Lucid Slugify

Works with @adonisjs/lucid 5.0.3 or greater.

This addon adds the functionality to generate unique slugs for multiple fields when saving them to the database using Lucid models.

NPM Version Build Status Coveralls

Installation

Make sure to install it using adonis install over npm or yarn.

adonis install @adonisjs/lucid-slugify

# yarn
adonis install @adonisjs/lucid-slugify --yarn

Next, make sure to read the instructions.md file.

Features ⭐️

  1. Url friendly strings.
  2. Handles unicode
  3. Works seamlessly with updates
  4. Uniqueness guaranteed.

Using trait ✍️

const Model = use('Model')

class Post extends Model {
  static boot () {
    super.boot()

    this.addTrait('@provider:Lucid/Slugify', {
      fields: { slug: 'title' },
      strategy: 'dbIncrement',
      disableUpdates: false
    })
  }
}

Options

{
"fields":

key/value pair of fields, you want to generate slugs for..

The key is the slug field and value is the field whose value will be used for generating the slug

"strategy":

It can be raw function or one of the predefined strategies.

"disableUpdates":

Depending upon your app, you may never want slugs to change. So disable the update hook.

}

In action πŸ€ΎπŸ»β€β™‚οΈ

Let's use the post model and generate a new slug.

const post = new Post()
post.title = 'Adonis 101'
await post.save()

console.log(post.slug) // adonis-101

On update it will re-generate the slug, only if the value of the actual field has been changed.

const post = await Post().find(1)
await post.save() // noop

post.title = 'A new title'
await post.save() // slug re-generated

console.log(post.slug) // a-new-title

Strategies πŸ‘©πŸ»β€πŸ”¬

Generating unique slugs is a hard problem, since each application has unique requirements and constraints. So instead of defining a single strategy to generate slugs, the add-on keeps it flexible to define custom strategies.

2 different strategies are shipped by default and here's how they work.


dbIncrement

The dbIncrement strategy adds a counter to the slugs, when duplicate slugs are found.

Works great with mysql and pg, whereas sqlite has some edge cases.

Let's assume a post with slug -> hello_world already exists.

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

Now if we will add a new slug, it will add -1, -2 respectively to it.

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

As mentioned above, this strategy fails in some edge cases when using sqlite. Let's take the following database table structure.

+----+-------------------+-------------------+
| id | title             | slug              |
+----+-------------------+-------------------+
| 1  | Hello world       | hello-world       |
| 2  | Hello world       | hello-world-1     |
| 3  | Hello world       | hello-world-2     |
| 4  | Hello world fanny | hello-world-fanny |
+----+-------------------+-------------------+

In sqlite adding a new post with Hello world title will fail and here's why.

  1. The strategy will find latest row from the database matching slug LIKE hello-world%.
  2. The return row will be hello-world-fanny and not hello-world-2 and hence the counter logic will fail.
  3. In MySQL and PostgreSQL, we make use of Regular expressions REGEXP ^hello-world(-[0-9*])?$ and pull the correct row.

shortId

This strategy works great regardless of the database engine in use, the only downside is, the URL's are not as pretty as dbIncrement strategy.

Make sure to install shortid as dependency before using it

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

The random number after the actual slug is generated using shortid and it ensures uniqueness.

Defining custom strategies πŸ‘¨πŸ»β€πŸ’»

You can define your strategies just by defining a function, and here's what a strategy is responsible for.

  1. It receives the base slug generated by the core library.
  2. It should return back a string ensuring the return value is unique.
this.addTrait('@provider:Lucid/Slugify', {
  fields: { slug: 'title' },
  strategy: async (field, value, modelInstance) => {
    return `${modelInstance.author_id}-${value}`
  }
})

The above is a simple implementation where we prepend the author_id to slug and ofcourse, we assumed that author_id will be set on the model instance.

const post = new Post()
post.author_id = auth.user.id

post.title = 'Spacex Launch'
await post.save()

console.log(post.slug) // 1-spacex-launch

How about more strategies?

You just need to find the one that fits your needs. Wordpress has been generating slugs for years and here's a screenshot of options they give.

Tests

The code is tested with mysql, pg and sqlite only. Tests are written using japa.