Example: E-Commerce Catalog

Products, variants, categories, stock, and storefront search. A complete catalog with the data model that makes it fast.

A catalog looks relational but shouldn't be modeled that way on RUAL: products carry everything their page shows. This example covers the model, the storefront, and stock handling that can't double-sell.

The Product Document

Product Document
{
  "_meta": {
    "cms": 1780531200118,
    "created": 1780531200,
    "entity": 1,
    "expiry": -1,
    "guid": "c45797061f2c15a75361042244385e96419d230ab804d7fa08cd52981fd78192",
    "removed": 0,
    "ums": 1782166695204,
    "update_hash": "f1a9c2d47b6035e8419dc0a7532be96481cd30fa",
    "updated": 1782166695
  },
  "category": "apparel",
  "image": "/static/products/tshirt-black.webp",
  "name": "essential cotton t-shirt",
  "price": 2490,
  "sku": "TSHIRT-BLK-M",
  "status": "active",
  "variants": [
    {
      "size": "m",
      "sku": "TSHIRT-BLK-M",
      "stock": 14
    },
    {
      "size": "l",
      "sku": "TSHIRT-BLK-L",
      "stock": 3
    }
  ]
}
  • Variants live inside the product. A product page reads one document. Never joins (see catalog model).
  • Price in cents with default_divide_by = 100: no float rounding bugs.
  • Everything stored lowercase so case-sensitive search just works.

The Storefront

  1. Category page: term filter on category + status = active, sorted by name, paginated with limit/offset.
  2. Search box: query_bool_simple_query_string_field on name wrapped in query_bool_must, with the active-filter in query_bool_filter via query_and.
  3. Product page: fetch by sku with function_search_single_result; render variants from the embedded array, disabling out-of-stock sizes in the UI.
  4. Autocomplete: prefix/wildcard query on name behind a small API. The pattern in search optimization.

Stock Without Double-Selling

  1. Checkout decrements stock with mutations_increment_by_field (-quantity) inside function_update_document: mutation transactions process sequentially, so two checkouts can't overwrite each other.
  2. Guard the sale: only decrement when stock >= quantity; on the failure path, hold the order as backorder instead of rejecting the customer.
  3. Restock is the same increment with a positive value. One block, both directions.

Admin Side

Next Steps

Storage Examples The catalog and order models in full. Inventory Tracker Stock-focused variant of this example. Creating a REST API Expose the catalog to other systems.

Frequently asked

How do I model product variants in RUAL storage?

Keep variants inside the product document as an embedded array, so a product page reads one document and never joins. Render the variants from that array and disable out-of-stock sizes in the UI.

How do I prevent double-selling stock in RUAL?

Decrement stock with mutations_increment_by_field inside function_update_document, guarded by a stock >= quantity check. Mutation transactions process sequentially per document, so two checkouts cannot overwrite each other, and on the failure path you hold the order as backorder instead of rejecting the customer.

Why store prices in cents in a RUAL catalog?

Storing the price in cents with default_divide_by = 100 avoids float rounding bugs, and the display divides it back. The same model stores names in lowercase so case-sensitive search just works.