Developer ResourcescatalogCategory
Category Module Backend Documentation
Backend architecture and invariants for catalog category flows.
Summary
Category backend manages:
- hierarchical categories with max depth
3 - slug generation + slug history continuity
- soft delete and restore lifecycle
- customer/mobile category discovery
- product kind tagging for storefront filtering
Data Model
categorytable- hierarchy:
parent_id -> category.id - soft delete:
deleted_at,deleted_by - visibility:
is_visible - ordering:
order - product kind:
product_kind(nullable enum:physical,digital,both)
- hierarchy:
category_slug_history- tracks old slugs for legacy URL resolution
Product Kind Enum
CREATE TYPE category_product_kind AS ENUM ('physical', 'digital', 'both');Separate from the product_kind enum on the products table. Category kind represents which storefront the category belongs to, not the product's delivery format.
NULL means the category has no kind constraint (legacy or cross-kind category).
Core Invariants
- category cannot be its own parent
- category cannot be moved under descendant
- depth cannot exceed 3 levels
- soft delete is blocked if active children exist
- restore is blocked if parent is soft-deleted
- parent-wise reorder allows only siblings under same
parentId
Product Kind Filtering
Admin
Admin filters use exact match: WHERE product_kind = $filter.
Customer
Customer filter expands to include both categories:
WHERE product_kind = $filter OR product_kind = 'both'This ensures that when the physical storefront queries productKind=physical, categories intended for both storefronts are also returned.
Soft Delete Lifecycle
Delete
- endpoint:
DELETE /api/admin/categories/:id - behavior: sets
deleted_at,deleted_by - does not physically remove row
Restore
- endpoint:
POST /api/admin/categories/:id/restore - behavior:
- validates parent and depth rules against active tree
- resolves slug conflicts with suffix strategy (
slug,slug-1,slug-2) - reactivates category by clearing
deleted_at,deleted_by
Query Behavior
- admin list/detail excludes soft-deleted by default
- admin can include them via
includeDeleted=true - customer/mobile always exclude soft-deleted
- slug-history fallback returns only visible + active category
Caching
Category mutations invalidate:
catalog:categories:visible*catalog:categories:list:*catalog:category:slug:*
Customer list cache key includes productKind as a segment. Different kind filter values produce separate cache entries.