ByteBite Docs
Architecture

Guest cart

Tenant-isolated storefront cart with a host-only cookie and live menu prices.

Guest cart

The third product slice is Cart → Storefront. A guest adds active menu items to a cart that belongs to the restaurant resolved from the hostname.

Prices are not snapshotted on the cart. Reads and mutations resolve name, price_cents, and is_active live from menu_items. Snapshots happen at checkout. Cart GET stays 200 when the restaurant is closed; pickup submit is gated there.

Tables

carts

ColumnNotes
idUUID. Value stored in the bb_cart cookie
restaurant_idFK → restaurants.id (ON DELETE restrict)
created_at / updated_atTimestamps

cart_items

ColumnNotes
idUUID. Path :itemId on PATCH/DELETE
cart_idFK → carts.id (ON DELETE cascade)
restaurant_idDefense-in-depth copy of the tenant key
menu_item_idFK → menu_items.id
quantityInteger 1..99
created_at / updated_atTimestamps

Unique (cart_id, menu_item_id). Adding the same item again increases quantity and caps at 99.

Seeds do not create carts.

The cookie value is the cart UUID. Fastify sets and rotates it. The browser is not the source of truth (no localStorage).

AttributeValue
Namebb_cart
httpOnlyyes
SameSiteLax
Domainomitted (host-only). Luigi's cookie never reaches Mario
Secureyes when NODE_ENV === "production" (staging and production)
Path/
Max-Age30 days

Resolution:

  • No cookie on GET → 200 with items: []. No insert, no Set-Cookie
  • Cookie UUID bound to the resolved restaurant → that cart
  • Missing, foreign, or other-restaurant UUID → ignore, create a new cart for the resolved tenant, rotate the cookie (no leak)
  • First mutation without a cookie → create a cart and set the cookie
  • DELETE /storefront/cart empties lines; the cookie may remain

Public API

Cart routes use the same hostname tenant gate as GET /storefront/menu.

  • Unknown or inactive restaurant → 404 (Restaurant not found)
  • Database failure during hostname lookup → 503
  • Query/body restaurantId is ignored
  • Empty cart → 200 with items: [], not 404
MethodPathBody
GET/storefront/cart—
POST/storefront/cart/items{ menuItemId, quantity? } (default 1)
PATCH/storefront/cart/items/:itemId{ quantity } (0 deletes the line)
DELETE/storefront/cart/items/:itemId—
DELETE/storefront/cart—

:itemId is the cart line UUID, not menuItemId.

Example (Luigi, two Margherita):

{
  "restaurantId": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaa1",
  "items": [
    {
      "id": "<cart_item uuid>",
      "menuItemId": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaa5",
      "name": "Margherita",
      "quantity": 2,
      "unitPriceCents": 850,
      "lineTotalCents": 1700
    }
  ],
  "itemCount": 2,
  "subtotalCents": 1700
}

Inactive or cross-tenant items cannot be added (404). If such a line is already stored, GET omits it and PATCH/DELETE on that line returns 409. Inactive seed items (Secret Calzone, Secret Lasagna) never appear in a public cart.

There is no dashboard cart API.

Storefront

The tenant start page SSR-loads the cart (API_INTERNAL_URL + X-ByteBite-Tenant-Host + incoming Cookie, cache: "no-store"). Browser mutations use same-origin /api with credentials so bb_cart stays host-only.

The technical host storefront.bytebite.test stays a placeholder and does not load a cart.

Not in this slice

Payment providers, delivery, variants/extras/allergens, minimum order, delivery fee, object storage, dashboard cart, production deploy. Weekly hours and checkout gating: Opening hours. Checkout lives in Checkout & orders.

On this page