# RealWorld API Slug Generation: Algorithm and Implementation

> Discover the algorithm behind RealWorld API slug generation. Learn how article titles are normalized and combined with user IDs for unique slugs.

- Repository: [Thinkster/realworld](https://github.com/gothinkster/realworld)
- Tags: internals
- Published: 2026-02-28

---

**RealWorld API generates article slugs by normalizing the article title with the slugify library and appending the author's numeric user ID to guarantee uniqueness.**

The RealWorld API, maintained in the gothinkster/realworld repository, implements a deterministic slug generation strategy that transforms article titles into URL-friendly identifiers. This RealWorld API slug generation process combines third-party text normalization with a per-author identifier suffix to prevent collisions across the platform.

## RealWorld API Slug Generation Algorithm

The slug creation process follows a deterministic two-stage pipeline: first normalizing the title string, then appending a unique author identifier.

### Title Normalization with the slugify Library

The backend imports the third-party **slugify** npm package (version 1.6.0) as declared in [`apps/api/package.json`](https://github.com/gothinkster/realworld/blob/main/apps/api/package.json). According to the source code, this library applies three transformations by default: converting the string to lowercase, replacing any run of non-alphanumeric characters with a single hyphen (`-`), and trimming leading or trailing hyphens.

```ts
// Simplified view of slugify's algorithm (default options)
// 1) toLowerCase()
// 2) replace(/[^a-z0-9]+/g, '-')
// 3) trim(/^[-]+|[-]+$/g, '')

```

### Author ID Suffix for Global Uniqueness

To guarantee uniqueness even when two users create articles with identical titles, the service appends a hyphen and the authenticated user's numeric ID (`auth.id`). The composite format follows this pattern:

```ts
const slug = `${slugify(title)}-${auth.id}`;

```

This ensures that `my-article-title-1` and `my-article-title-2` are distinct slugs belonging to different authors.

### Database Uniqueness Validation

Before persisting a new article, the API queries the database for an existing record with the same slug. If found, the server returns a **422 error** indicating the title must be unique. During article updates, the system recomputes the slug only if the title field changes, maintaining stable URLs for unchanged content.

## Route Handler Implementation

All slug generation logic lives in the article route handlers within the `apps/api` directory, with specific implementations for creation and update operations.

### Article Creation in index.post.ts

In [`apps/api/server/routes/api/articles/index.post.ts`](https://github.com/gothinkster/realworld/blob/main/apps/api/server/routes/api/articles/index.post.ts) at line 24, the POST handler constructs the slug by combining the normalized title with the author ID extracted from the authentication context.

```ts
// ----------------------------------------------------
// 1️⃣ Create an article – slug generation (POST /api/articles)
// ----------------------------------------------------
import slugify from 'slugify';
import { definePrivateEventHandler } from '~/auth-event-handler';

export default definePrivateEventHandler(async (event, { auth }) => {
  const { article } = await readBody(event);
  const { title, description, body, tagList } = article;

  // ---- slug generation ----
  //   1. Normalise title with slugify (lower‑case, non‑alphanum → '-')
  //   2. Append author id to guarantee uniqueness
  const slug = `${slugify(title)}-${auth.id}`;

  // …store article in DB using `slug`…
});

```

### Conditional Regeneration in index.put.ts

In `apps/api/server/routes/api/articles/[slug]/index.put.ts` at line 37, the PUT handler conditionally regenerates the slug only when the request body includes a modified title. This prevents breaking existing article URLs when updating only the body or description.

```ts
// ----------------------------------------------------
// 2️⃣ Update an article – conditional slug regeneration (PUT /api/articles/:slug)
// ----------------------------------------------------
import slugify from 'slugify';
import { definePrivateEventHandler } from '~/auth-event-handler';

export default definePrivateEventHandler(async (event, { auth }) => {
  const { article } = await readBody(event);
  const currentSlug = getRouterParam(event, 'slug');

  let newSlug: string | null = null;
  if (article.title) {
    // Regenerate slug only if title changed
    newSlug = `${slugify(article.title)}-${auth.id}`;
  }

  // …apply `newSlug` (if any) when updating the DB…
});

```

## Key Source Files and Dependencies

The RealWorld API slug generation relies on the following specific files in the gothinkster/realworld repository:

- **[`apps/api/server/routes/api/articles/index.post.ts`](https://github.com/gothinkster/realworld/blob/main/apps/api/server/routes/api/articles/index.post.ts)** – Contains the slug creation logic at line 24 for new articles.
- **`apps/api/server/routes/api/articles/[slug]/index.put.ts`** – Handles conditional slug recomputation at line 37 when titles are updated.
- **[`apps/api/package.json`](https://github.com/gothinkster/realworld/blob/main/apps/api/package.json)** – Declares the **slugify** dependency at version 1.6.0.

## Summary

- The RealWorld API uses the **slugify** library (v1.6.0) to normalize article titles by converting to lowercase and replacing non-alphanumeric characters with hyphens.
- The algorithm appends the author's numeric user ID (`auth.id`) to the normalized title to guarantee uniqueness across the platform.
- The database validates slug uniqueness before persistence, returning a **422 error** if collisions are detected.
- During updates, slugs are only regenerated if the article title changes, preserving URL stability for other modifications.
- All logic is implemented in the route handlers at [`apps/api/server/routes/api/articles/index.post.ts`](https://github.com/gothinkster/realworld/blob/main/apps/api/server/routes/api/articles/index.post.ts) and `[slug]/index.put.ts`.

## Frequently Asked Questions

### What npm package does RealWorld use for slug generation?

The RealWorld API uses the **slugify** package version 1.6.0, as specified in [`apps/api/package.json`](https://github.com/gothinkster/realworld/blob/main/apps/api/package.json). This library handles the conversion of titles to lowercase and replaces sequences of non-alphanumeric characters with single hyphens.

### How does RealWorld ensure article slugs are unique?

The API appends the author's numeric user ID to the normalized title string, creating a composite slug like `my-article-title-123`. Additionally, the system queries the database before persistence and returns a 422 error if a duplicate slug already exists for that author.

### Does updating an article always change its slug in RealWorld?

No, the slug is only regenerated during PUT requests when the request body includes a `title` field. As implemented in `apps/api/server/routes/api/articles/[slug]/index.put.ts`, the handler checks for the presence of `article.title` before computing a new slug value, ensuring existing URLs remain valid when updating other fields.

### What happens if two different users create articles with identical titles?

Because the slug format includes the user ID suffix (`slugify(title)-auth.id`), two different users will generate different slugs for identical titles (e.g., `my-title-1` vs `my-title-2`). However, if the same user attempts to create multiple articles with the same title, the database uniqueness check will reject the second attempt with a 422 error.