Using Nx to allow two projects to be run on the same repository
npm installNx workspace with:
web: React appapi: Express API
npx nx serve web
npx nx serve apinpx nx build web
npx nx build apiStart both apps:
npx nx serve api
npx nx serve webOpen the frontend:
http://127.0.0.1:4200Expected checks:
- The page shows the product picker, cart items, promo code input, discount breakdown, and promo stats sections.
- The frontend uses the Vite
/apiproxy and does not require hardcoded backend URLs in the browser.
Basic cart flow:
- Add TRT, Sermorelin, Semaglutide, or Ozempic from the product cards.
- Confirm cart rows appear with quantity controls and remove actions.
- Confirm the discount breakdown updates after a short debounce instead of on every keystroke or click.
Promo code flow:
- Apply valid promo codes such as
SAVE20,FLAT10,FLASH30, andVIP15. - Confirm applied discounts appear in the breakdown.
- Apply invalid or conflicting promo codes and confirm rejected reasons are shown in the UI.
- Apply duplicate or lowercase promo codes and confirm they normalize and dedupe correctly.
Persistence:
- Add items and promo codes, then refresh the page.
- Confirm the cart and applied promo codes restore from
localStorage.
Promo stats:
- Confirm the promo stats section loads on page render.
- Click the manual refresh button and confirm stats refresh successfully.
- Apply promo codes successfully and confirm stats update after refresh / polling.
Rate limit behavior:
- Trigger repeated cart recalculations by rapidly changing cart contents or promo codes.
- Confirm the frontend shows the backend cooldown message when rate limited.
- Confirm cart edits remain possible during cooldown and recalculation resumes after the cooldown expires.
npm run test:apiThis runs a lightweight Node test suite covering the promo engine, calculate endpoint validation and rate limiting, and promo stats usage tracking.
Start the backend:
npx nx serve apiHealth check:
curl -i http://localhost:3000/api/healthGet promo usage stats:
curl -i http://localhost:3000/api/promo/statsExpected checks:
- Response status is
200. - Response contains
success: true. - Response lists each promo code with
uses,max_uses,remaining_uses, andavailable.
Valid cart with stacked promos:
curl -i -X POST http://localhost:3000/api/cart/calculate \
-H 'Content-Type: application/json' \
-d '{
"user_id": "usr_123",
"items": [
{ "sku": "PROD-001", "quantity": 2, "price": "49.99" },
{ "sku": "PROD-002", "quantity": 1, "price": "149.99" }
],
"promo_codes": ["SAVE20", "FLAT10"]
}'Expected checks:
- Request prices are sent as fixed two-decimal strings.
subtotal,total_discount,final_total, and each discountamountare returned as fixed two-decimal strings.SAVE20applies beforeFLAT10.- The API server logs a line with masked
user_id, applied promo codes, total discount, and processing time.
Conflict case:
curl -i -X POST http://localhost:3000/api/cart/calculate \
-H 'Content-Type: application/json' \
-d '{
"user_id": "usr_conflict",
"items": [
{ "sku": "PROD-001", "quantity": 1, "price": "200.00" }
],
"promo_codes": ["SAVE20", "FLASH30", "VIP15"]
}'Expected checks:
- One of the conflicting priority-1 codes is rejected with
CONFLICT_WITH_<CODE>. - The accepted priority-1 code is applied before
VIP15.
Unknown promo code:
curl -i -X POST http://localhost:3000/api/cart/calculate \
-H 'Content-Type: application/json' \
-d '{
"user_id": "usr_unknown",
"items": [
{ "sku": "PROD-001", "quantity": 1, "price": "100.00" }
],
"promo_codes": ["NOTREAL", "FLAT10"]
}'Expected checks:
NOTREALappears inrejected_codeswithCODE_NOT_FOUND.FLAT10still applies.
Duplicate and normalized promo codes:
curl -i -X POST http://localhost:3000/api/cart/calculate \
-H 'Content-Type: application/json' \
-d '{
"user_id": "usr_dupe",
"items": [
{ "sku": "PROD-001", "quantity": 1, "price": "100.00" }
],
"promo_codes": ["SAVE20", "save20", " FLAT10 "]
}'Expected checks:
SAVE20is only applied once.- Lowercase and padded promo codes are normalized successfully.
Invalid payload:
curl -i -X POST http://localhost:3000/api/cart/calculate \
-H 'Content-Type: application/json' \
-d '{
"user_id": "",
"items": [
{ "sku": "PROD-001", "quantity": 0, "price": "-10.00" }
],
"promo_codes": ["SAVE20"]
}'Expected checks:
- Response status is
400. - Response contains
INVALID_CART_REQUEST.
Rate limit test:
for i in $(seq 1 11); do
echo "Request $i"
curl -s -i -X POST http://localhost:3000/api/cart/calculate \
-H 'Content-Type: application/json' \
-d '{
"user_id": "rate_limit_user",
"items": [
{ "sku": "PROD-001", "quantity": 1, "price": "10.00" }
],
"promo_codes": []
}'
echo
doneExpected checks:
- Requests 1 through 10 succeed.
- Request 11 returns
429. - The
429body includesRATE_LIMITEDandretry_after.