DEV Community

Cover image for πŸ—‚οΈ Designing a Scalable Category System for an E-Commerce App
AYON KARMAKAR
AYON KARMAKAR

Posted on • Edited on

πŸ—‚οΈ Designing a Scalable Category System for an E-Commerce App

When building an e-commerce application, categories look simple at first β€” until your product count grows and business asks for:

  • sub-categories
  • nested menus
  • breadcrumbs
  • SEO-friendly URLs
  • easy reordering

This README explains a scalable, production-ready category design used in real-world systems, without overengineering.


❌ The Common Mistake

Many apps start with this:

categories
sub_categories
sub_sub_categories
Enter fullscreen mode Exit fullscreen mode

This breaks immediately when:

  • you need more depth
  • hierarchy changes
  • queries become complex

βœ… The Scalable Solution (Single Categories Table)

Use one table with a self-reference.

categories
-----------
id          UUID / BIGINT (PK)
name        VARCHAR(255)
slug        VARCHAR(255) UNIQUE
parent_id   UUID / BIGINT (FK β†’ categories.id, NULL)
level       INT
path        VARCHAR(500)
sort_order  INT
is_active   BOOLEAN
created_at  TIMESTAMP
updated_at  TIMESTAMP
Enter fullscreen mode Exit fullscreen mode

This supports unlimited nesting and clean queries.

🌳 How Hierarchy Works

Example structure

Electronics
 └── Mobiles
      β”œβ”€β”€ Smartphones
      └── Feature Phones
Enter fullscreen mode Exit fullscreen mode

Stored data

id name parent_id level path sort_order
1 Electronics NULL 0 1 1
2 Mobiles 1 1 1/2 1
3 Smartphones 2 2 1/2/3 1
4 Feature Phones 2 2 1/2/4 2

πŸ”‘ Field Breakdown (The Important Part)

1️⃣ slug – URL-friendly identifier

A slug is a readable string used in URLs.

Example:

"Smart Phones" β†’ "smart-phones"
Enter fullscreen mode Exit fullscreen mode

Used for:

/category/electronics/mobiles/smart-phones
Enter fullscreen mode Exit fullscreen mode

Why slugs matter:

  • SEO friendly
  • Stable URLs
  • No exposed IDs

2️⃣ level – Depth of the category

level tells how deep a category is.

level 0 = root category
level 1 = sub-category
level 2 = sub-sub-category
Enter fullscreen mode Exit fullscreen mode

Why it exists:

  • Show only top-level categories on homepage
  • Restrict max depth
  • Simple filtering

Query example:

SELECT * FROM categories WHERE level = 0;
Enter fullscreen mode Exit fullscreen mode

3️⃣ path – Full hierarchy (Materialized Path)

path stores the entire lineage from root β†’ current node.

Example:

Electronics β†’ Mobiles β†’ Smartphones
path = "1/2/3"
Enter fullscreen mode Exit fullscreen mode

Why it's powerful:

  • Fetch entire subtrees without recursion
  • Build breadcrumbs easily
  • Generate SEO URLs

Query example:

SELECT * FROM categories WHERE path LIKE '1/2/%';
Enter fullscreen mode Exit fullscreen mode

4️⃣ sort_order – Display control (NOT hierarchy)

sort_order controls how categories appear in UI.

Without it β†’ unpredictable order

With it β†’ business-controlled order

Query example:

ORDER BY sort_order ASC;
Enter fullscreen mode Exit fullscreen mode

Used for:

  • Navbar ordering
  • Featured categories
  • Seasonal rearrangements

πŸ’‘ Why Use level + path Together?

Use case level path
Top-level filtering βœ… ❌
Max depth validation βœ… ❌
Subtree queries ❌ βœ…
Breadcrumbs ❌ βœ…
SEO URLs ❌ βœ…

They solve different problems, not duplication.


🌟 Product Association

Products usually belong to the leaf category.

products
---------
id
name
slug
price
category_id β†’ categories.id
Enter fullscreen mode Exit fullscreen mode

🎯 Business Superpower β€” Multi-Level Product Visibility

A product usually belongs to the deepest (leaf) category.

Example:

Electronics β†’ Mobiles β†’ Smartphones  
                        ↑  
                 Samsung Galaxy
Enter fullscreen mode Exit fullscreen mode

But customers may browse or search from any level.

They might open:

  • Electronics
  • Mobiles
  • Smartphones

πŸ‘‰ In all cases, the product should appear.


🧠 How This Design Enables It

Because each category stores its path, we instantly know all its ancestors.

Smartphones β†’ path = "1/2/3"
Enter fullscreen mode Exit fullscreen mode

So the product automatically belongs to:

  • 1 β†’ Electronics
  • 1/2 β†’ Mobiles
  • 1/2/3 β†’ Smartphones

No extra mapping tables.

No complex recursion.


⚑ Example Queries

Show products under Mobiles (including all children)

SELECT p.*
FROM products p
JOIN categories c ON p.category_id = c.id
WHERE c.path LIKE '1/2/%';
Enter fullscreen mode Exit fullscreen mode

This returns items from:

  • Smartphones
  • Feature Phones
  • any future subcategory

Show products under Electronics

WHERE c.path LIKE '1/%';
Enter fullscreen mode Exit fullscreen mode

Boom πŸ’₯
Entire department.

πŸ’° Business Advantages

βœ… Better product discovery

Users don’t need perfect navigation.

βœ… Faster search & filtering

No recursive joins required.

βœ… Powerful marketing pages

Create pages like:

  • β€œBest Mobiles”
  • β€œTop Electronics Deals”

βœ… Smarter recommendations & ads

Target an entire branch of the catalog.

βœ… Future proof

Add deeper levels anytime β†’ queries still work.


πŸͺ Real Outcome

If a Samsung Galaxy s23 FE is stored in:

Electronics β†’ Mobiles β†’ Smartphones
Enter fullscreen mode Exit fullscreen mode

It will automatically appear in:

  • Electronics listing
  • Mobiles listing
  • Smartphones listing
  • search results
  • recommendations
  • breadcrumbs

✨ without extra logic.

πŸ† Final Recommendation

  • βœ… Single categories table
  • βœ… parent_id for structure
  • βœ… level for depth logic
  • βœ… path for fast reads
  • βœ… slug for clean URLs
  • βœ… sort_order for UI control

This design scales from startup MVP β†’ large marketplace without schema changes.


πŸ’¬ Interview One-Liner

A scalable category system uses a self-referencing table with materialized paths to support unlimited depth, fast reads, clean URLs, and UI-controlled ordering.

If you liked this design, feel free to ⭐ the repo or reuse it in your project.

Happy building πŸš€

Top comments (0)