Image derived from an original photo by Peter Stevens and adapted under the terms of its license.

What’s a slug

and why would I use one?

Dave Sag
ITNEXT
Published in
4 min readApr 17, 2019

--

A slug is a human-readable, unique identifier, used to identify a resource instead of a less human-readable identifier like an id. You use a slug when you want to refer to an item while preserving the ability to see, at a glance, what the item is.

Typically slugs are used when making search-engine optimised urls, so for example the url of this post is https://medium.com/@davesag/whats-a-slug-f7e74b6c23e0. There are actually two slugs in that url, my username, @davesag and the slug for this specific post, whats-a-slug-f7e74b6c23e0. Of that the whats-a-slug part comes from the title, and, for reasons only known to the people at Medium, they’ve added a short uuid to the end of it. The only reason I’d think of for doing that is if a user was able to write two stories with the same title, so I guess that can happen.

An example

A more interesting example is when your system stores a set of Products, and each product has a Category.

In pseudo-code, here’s a fairly typical set of models you’d see in a database.

Product {
id: integer (auto-incremented, unique),
name: string,
slug: string (derivedFrom(name), unique),
category: belongsTo(Category, id)
}
Category {
id: integer (auto-incremented, unique),
name: string,
slug: string (derivedFrom(name), unique),
products: hasMany(Products, id)
}

Now let’s say you want to offer an API that lists the products for a given category.

You could define a route as follows:

GET /category/:id/products

But that’s going to give you a rather ugly URL.

So it’s better to define the route as:

GET /category/:slug/products

So if you have a category ‘books’ then someone calling your API will call:

GET /category/books/products

Lean API Design

General principles

An API shall:

  • not return the internal id used within the database, but return a slug instead,
  • return the minimum amount of data to be useful, and
  • return enough data to minimise the need for repeated requests

Example data based on the model above

Category

{
id: 1,
name: "Books",
slug: "books"
}

Products

[
{
id: 1,
name: "Old Man's War",
slug: "old-mans-war",
category: 1
},
{
id: 2,
name: "All Systems Red",
slug: "all-systems-red",
category: 1
}
]

A call to GET /category/books would return:

{
name: "Books",
slug: "books",
products: [
{
name: "Old Man's War",
slug: "old-mans-war"
},
{
name: "All Systems Red",
slug: "all-systems-red"
}
]
}

There is no need to return the category information with each product as the requester already knows the category, however by the same logic there should also not be a need to return the category’s slug either.

For the sake of consistency it’s always good practice to return an item’s slug, even if that information is redundant. The consumer of the API can usually save itself some complexity if the slug is returned.

A more interesting example

Localisation is often handled by using a data structure of the form:

{
[locale]: string
}

In pseudo-code, here’s how that would be structured in the database:

Product {
id: integer (auto-incremented, unique),
name: hstore({
[locale]: string
}),
slug: string (derivedFrom(name), unique),
category: belongsTo(Category, id)
}
Category {
id: integer (auto-incremented, unique),
name: hstore({
[locale]: string
}),
slug: string (derivedFrom(name), unique),
products: hasMany(Products, id)
}

Each model would need an instance function like:

function getLocalised(field, locale) {
return this[field][locale]
}

The API would need a way to know which locale to return. This is typically done by either adding a locale param to the request, or in the request’s Accept-Language header, or if the request is authenticated, then you might associate the desired locale with a user record.

Example data based on the model above

Category

{
id: 1,
name: {
en: "Books",
vn: "Sách"
},
slug: "books"
}

Products

[
{
id: 1,
name: {
en: "Old Man's War",
vn: "Chiến tranh của lão già"
},
slug: "old-mans-war",
category: 1
},
{
id: 2,
name: {
en: "All Systems Red",
vn: "Tất cả các hệ thống đều đỏ"
},
slug: "all-systems-red",
category: 1
}
]

A request to GET /product/all-systems-red?locale=vn would return:

{
name: "Tất cả các hệ thống đều đỏ",
slug: "all-systems-red",
category: {
name: "Sách",
slug: "books"
}
}

Seeding your database

When setting up a system you want to avoid having to specify object ids in seed data. Seed data is often defined in a yml file. Using the database model defined above we can write a seed file as follows:

seed-data.yml

- slug: books
name:
en: Books
vn: Sách
products:
- slug: old-mans-war
name:
en: Old Man's War
vn: Chiến tranh của lão già
- slug: all-systems-red
name:
en: All Systems Red
vn: Tất cả các hệ thống đều đỏ

Data like this are easy to import.

Logging

When writing out information to logs it’s fairly normal for those logs to be intended for both human and machine consumption.

It’s generally a bad idea to write ids out into logs. Writing out slugs on the other hand makes logs easily readable by people, and because slugs function as ids as well, they are still useful for machines.

How to make a slug

A good slug is short, descriptive, lower-case, has no accents, or ambiguous or hard to read-at-a-glance characters, and is unique.

Links

Slugify in different langauges

Books

Like this but not a subscriber? You can support the author by joining via davesag.medium.com.

--

--