@molecule/api-resource-cart
API resource · cart · API (Node) · v1.0.3 · Apache-2.0
Shopping cart resource with item management, coupon support, and computed totals.
npm install @molecule/api-resource-cartHow it works
@molecule/api-resource-cart is an API resource: the routes, validation and storage for cart, built on the database and auth cores so it runs on whichever providers your app has bonded.
import { routes, requestHandlerMap } from '@molecule/api-resource-cart'Works with: @molecule/api-i18n, @molecule/api-resource
Live demos
Running apps built with Molecule that install this package — open the app or study the template.
Reference
Shopping cart resource for molecule.dev.
Provides a user-scoped singleton cart with item management, coupon support, and computed totals (subtotal, discount, tax, total).
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Quick Start
import { routes, requestHandlerMap } from '@molecule/api-resource-cart'
Type
resource
Installation
npm install @molecule/api-resource-cart @molecule/api-database @molecule/api-i18n @molecule/api-logger @molecule/api-resource
API
Interfaces
CartItem
A shopping cart item.
interface CartItem {
/** Unique identifier for this cart item entry. */
id: string
/** The product being purchased. */
productId: string
/** Optional variant (size, color, etc.). */
variantId?: string
/** Display name of the product. */
name: string
/** Unit price of the item. */
price: number
/** Quantity in the cart. */
quantity: number
/** Optional product image URL. */
image?: string
/** Arbitrary metadata attached to this item. */
metadata?: Record<string, unknown>
}
AddCartItemInput
Input for adding an item to the cart.
interface AddCartItemInput {
/** The product to add. */
productId: string
/** Optional variant identifier. */
variantId?: string
/** Display name of the product. */
name: string
/** Unit price. */
price: number
/** Quantity to add. */
quantity: number
/** Optional product image URL. */
image?: string
/** Arbitrary metadata. */
metadata?: Record<string, unknown>
}
UpdateCartItemInput
Input for updating an item's quantity.
interface UpdateCartItemInput {
/** New quantity for the item. Must be >= 1. */
quantity: number
}
AppliedCoupon
A coupon applied to a cart.
interface AppliedCoupon {
/** The coupon code. */
code: string
/** The discount type. */
type: 'percentage' | 'fixed'
/** The discount value (percentage 0–100, or fixed amount). */
value: number
}
Cart
A shopping cart with items, totals, and optional coupon.
interface Cart {
/** Unique cart identifier. */
id: string
/** Owner of this cart. */
userId: string
/** Items in the cart. */
items: CartItem[]
/** Currently applied coupon, if any. */
coupon?: AppliedCoupon
/** Sum of item prices × quantities before discounts. */
subtotal: number
/** Discount amount from applied coupon. */
discount: number
/** Computed tax amount. */
tax: number
/** Final total: subtotal − discount + tax. */
total: number
/** Last modification timestamp. */
updatedAt: string
}
CartSummary
Summary snapshot of a cart.
interface CartSummary {
/** Total number of items (sum of quantities). */
itemCount: number
/** Number of distinct products. */
uniqueItems: number
/** Subtotal before discounts. */
subtotal: number
/** Final total after discounts and tax. */
total: number
}
ApplyCouponInput
Input for applying a coupon.
interface ApplyCouponInput {
/** The coupon code to apply. */
code: string
}
CartRow
Internal database row for a cart.
interface CartRow {
/** Unique cart identifier. */
id: string
/** Owner of this cart. */
userId: string
/** JSON-serialized coupon data, or null. */
coupon: string | null
/** Creation timestamp. */
createdAt: string
/** Last modification timestamp. */
updatedAt: string
}
CartItemRow
Internal database row for a cart item.
interface CartItemRow {
/** Unique cart item identifier. */
id: string
/** Parent cart identifier. */
cartId: string
/** The product being purchased. */
productId: string
/** Optional variant identifier. */
variantId: string | null
/** Display name. */
name: string
/** Unit price. */
price: number
/** Quantity. */
quantity: number
/** Optional product image URL. */
image: string | null
/** JSON-serialized metadata. */
metadata: string | null
/** Creation timestamp. */
createdAt: string
}
Functions
addItem(req, res)
Adds an item to the authenticated user's cart. If the same product+variant already exists, increments the quantity instead of creating a duplicate entry.
⚠️ CLIENT-PRICE TRUST BOUNDARY ⚠️ — the stored price comes verbatim from
the request body. This resource is GENERIC (no product/catalog table), so it
CANNOT verify the price; the validation below only rejects malformed money
(negative price, non-integer/zero quantity). NEVER charge a customer off a
cart-item price: at checkout, re-resolve every unit price SERVER-SIDE from
the product/menu table (keyed by productId/variantId) and recompute
totals from those trusted values, ignoring the client-supplied price.
function addItem(req: MoleculeRequest, res: MoleculeResponse): Promise<void>
req— The request withAddCartItemInputbody.res— The response object.
applyCoupon(req, res)
Applies a coupon code to the user's cart. The coupon is validated by looking
it up in the coupons table. Replaces any previously applied coupon.
function applyCoupon(req: MoleculeRequest, res: MoleculeResponse): Promise<void>
req— The request withApplyCouponInputbody.res— The response object.
clearCart(_req, res)
Removes all items and the applied coupon from the user's cart.
function clearCart(_req: MoleculeRequest, res: MoleculeResponse): Promise<void>
_req— The request object (unused).res— The response object.
getCart(_req, res)
Returns the authenticated user's cart. Creates an empty cart if none exists.
function getCart(_req: MoleculeRequest, res: MoleculeResponse): Promise<void>
_req— The request object (unused — cart is identified by session user).res— The response object.
getCartSummary(_req, res)
Returns a lightweight summary of the user's cart (item count, totals).
function getCartSummary(_req: MoleculeRequest, res: MoleculeResponse): Promise<void>
_req— The request object (unused).res— The response object.
removeCoupon(_req, res)
Removes the currently applied coupon from the user's cart.
function removeCoupon(_req: MoleculeRequest, res: MoleculeResponse): Promise<void>
_req— The request object (unused).res— The response object.
removeItem(req, res)
Removes an item from the user's cart by item ID.
function removeItem(req: MoleculeRequest, res: MoleculeResponse): Promise<void>
req— The request withitemIdroute parameter.res— The response object.
updateQuantity(req, res)
Updates the quantity of a specific item in the user's cart.
function updateQuantity(req: MoleculeRequest, res: MoleculeResponse): Promise<void>
req— The request withitemIdparam andUpdateCartItemInputbody.res— The response object.
toCartItem(row)
Converts a database cart-item row into a typed CartItem.
function toCartItem(row: CartItemRow): CartItem
row— The raw database row.
Returns: The deserialized cart item.
computeSubtotal(items)
Computes the subtotal from a list of cart items (price × quantity).
function computeSubtotal(items: CartItem[]): number
items— The cart items.
Returns: The subtotal amount.
computeDiscount(coupon, subtotal)
Computes the discount from an applied coupon and a subtotal.
function computeDiscount(coupon: AppliedCoupon | undefined, subtotal: number): number
coupon— The applied coupon, if any.subtotal— The cart subtotal.
Returns: The discount amount (clamped to subtotal).
computeTax(taxableAmount)
Computes tax from the taxable amount.
function computeTax(taxableAmount: number): number
taxableAmount— The amount to tax (subtotal − discount).
Returns: The tax amount.
assembleCart(cartRow, itemRows)
Assembles a full Cart object from a database cart row and item rows.
function assembleCart(cartRow: CartRow, itemRows: CartItemRow[]): Cart
cartRow— The cart database row.itemRows— The cart item database rows.
Returns: The assembled cart.
Constants
i18nRegistered
Whether i18n registration has been attempted. Always true; this module is
a placeholder for symmetry with locale-bonded resources.
const i18nRegistered: true
requestHandlerMap
Handler map for the cart resource routes.
const requestHandlerMap: {
readonly getCart: typeof getCart
readonly addItem: typeof addItem
readonly updateQuantity: typeof updateQuantity
readonly removeItem: typeof removeItem
readonly clearCart: typeof clearCart
readonly applyCoupon: typeof applyCoupon
readonly removeCoupon: typeof removeCoupon
readonly getCartSummary: typeof getCartSummary
}
routes
Shopping cart routes. The cart is a user-scoped singleton resource
(one cart per authenticated user), so routes use /cart (singular).
const routes: readonly [
{
readonly method: 'get'
readonly path: '/cart'
readonly handler: 'getCart'
readonly middlewares: readonly ['authenticate']
},
{
readonly method: 'post'
readonly path: '/cart/items'
readonly handler: 'addItem'
readonly middlewares: readonly ['authenticate']
},
{
readonly method: 'put'
readonly path: '/cart/items/:itemId'
readonly handler: 'updateQuantity'
readonly middlewares: readonly ['authenticate']
},
{
readonly method: 'delete'
readonly path: '/cart/items/:itemId'
readonly handler: 'removeItem'
readonly middlewares: readonly ['authenticate']
},
{
readonly method: 'delete'
readonly path: '/cart'
readonly handler: 'clearCart'
readonly middlewares: readonly ['authenticate']
},
{
readonly method: 'post'
readonly path: '/cart/coupon'
readonly handler: 'applyCoupon'
readonly middlewares: readonly ['authenticate']
},
{
readonly method: 'delete'
readonly path: '/cart/coupon'
readonly handler: 'removeCoupon'
readonly middlewares: readonly ['authenticate']
},
{
readonly method: 'get'
readonly path: '/cart/summary'
readonly handler: 'getCartSummary'
readonly middlewares: readonly ['authenticate']
},
]
Injection Notes
Requirements
Peer dependencies:
@molecule/api-database^1.0.1@molecule/api-i18n^1.0.1@molecule/api-logger^1.0.1@molecule/api-resource^1.0.1
Runtime Dependencies
@molecule/api-database@molecule/api-i18n@molecule/api-logger@molecule/api-resource
SECURITY — cart item prices are CLIENT-SUPPLIED and unverified. This
resource is GENERIC (no product/catalog table), so addItem() stores the
price from the request body verbatim; the computed subtotal/discount/
tax/total (see utilities.ts) are derived from those client prices.
Input validation rejects malformed money (negative price, non-integer or
< 1 quantity) but does NOT establish that the prices are CORRECT — a
client can submit price: 0. NEVER charge a customer off a cart total: at
checkout, re-resolve every unit price SERVER-SIDE from the product/menu table
(keyed by productId/variantId) and recompute the totals from those
trusted values, ignoring the client-supplied price. The cart total is for
DISPLAY only. (applyCoupon() is already safe — it looks the coupon up in
the coupons table and ignores any client-supplied discount value.)
Migration required — and coupons is NOT included.
src/__setup__/carts.sql creates carts + cart_items (scaffolded apps
apply it automatically). applyCoupon() looks coupons up in a coupons
table this package does NOT create — provide one (your own migration or a
commerce package that ships it) before surfacing coupon UI, or the mounted
POST /cart/coupon route fails at runtime.
The cart is a per-user singleton, keyed by the session. GET /cart
creates the row on first read; never accept a cartId/userId from the
client — all routes operate on the caller's own cart.
E2E Tests
Integration checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual screens/flows, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip:
- Adding a product (
POST /cart/items) puts a line in the cart at the quantity requested, and the cart view + subtotal reflect it. - Adding the SAME product (same
productId/variantId) again MERGES — the existing line's quantity increments, it does NOT create a second duplicate line; a different product stays its own line. - Changing a line's quantity (
PUT /cart/items/:itemId) and removing a line (DELETE /cart/items/:itemId) update the cart, and the computed subtotal/total RECOMPUTE correctly: subtotal is the sum of price × quantity, and total = subtotal - discount + tax (tax is 0 until you wire a rate). Apply a coupon (POST /cart/coupon, looked up server-side in thecouponstable) and confirm discount + total drop; removing it restores them. - An invalid quantity (0, negative, or non-integer) and a negative price are REJECTED with a visible error and leave the cart unchanged — the totals shown are for DISPLAY only; a real checkout must re-resolve prices server-side.
- Clearing the cart (
DELETE /cart) empties it — items and any applied coupon gone, totals back to 0. If the app builds checkout, it hands off the right items and a SERVER-recomputed total, never the client cart total. - AUTHORIZATION — the cart is a per-user singleton keyed by the session;
no route takes a
cartId/userId. A second signed-in user sees only their own cart, and a lineitemIdfrom another user's cart is neither readable nor mutable (update/remove return not-found) — no path to someone else's cart.