PageSourceSearch

https://beeceptor.com/docs/assets/js/60a28f69.83cdc7c1.js

js beeceptor.com collected 2026-10-02 10:49:33 UTC 31,445 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunkbeeceptor_docs=self.webpackChunkbeeceptor_docs||[]).push([["6266"],{26660(e,s,t){t.r(s),t.d(s,{metadata:()=>n,default:()=>h,frontMatter:()=>i,contentTitle:()=>d,toc:()=>l,assets:()=>o});var n=JSON.parse('{"id":"tutorials/build-dynamic-api-workflows-with-response-templates","title":"Build a Stateful Shopping Cart Using Beeceptor","description":"Learn to build a stateful shopping cart API using Beeceptor response templates. Read request data, persist state, retrieve JSON arrays to personalize user experiences, and simulate checkout workflows.","source":"@site/docs/tutorials/accessing-request-data-formatting-json-virtual-api-response-templates.md","sourceDirName":"tutorials","slug":"/tutorials/build-dynamic-api-workflows-with-response-templates","permalink":"/docs/tutorials/build-dynamic-api-workflows-with-response-templates","draft":false,"unlisted":false,"tags":[{"inline":true,"label":"Tutorials","permalink":"/docs/tags/tutorials"},{"inline":true,"label":"Response Templates","permalink":"/docs/tags/response-templates"},{"inline":true,"label":"Stateful Mocking","permalink":"/docs/tags/stateful-mocking"},{"inline":true,"label":"JSON","permalink":"/docs/tags/json"}],"version":"current","sidebarPosition":6,"frontMatter":{"id":"build-dynamic-api-workflows-with-response-templates","slug":"/tutorials/build-dynamic-api-workflows-with-response-templates","title":"Build a Stateful Shopping Cart Using Beeceptor","description":"Learn to build a stateful shopping cart API using Beeceptor response templates. Read request data, persist state, retrieve JSON arrays to personalize user experiences, and simulate checkout workflows.","sidebar_label":"Build a Stateful Shopping Cart Using Beeceptor","sidebar_position":6,"tags":["Tutorials","Response Templates","Stateful Mocking","JSON"],"date":"2026-07-10T00:00:00.000Z"},"sidebar":"tutorialSidebar","previous":{"title":"Building a Stateful Movie Booking Flow","permalink":"/docs/tutorials/building-multi-step-user-journeys-using-api-mocking"},"next":{"title":"Simulate Gemini Errors","permalink":"/docs/tutorials/gemini-AI-error-simulation"}}'),r=t(74848),a=t(28453);let i={id:"build-dynamic-api-workflows-with-response-templates",slug:"/tutorials/build-dynamic-api-workflows-with-response-templates",title:"Build a Stateful Shopping Cart Using Beeceptor",description:"Learn to build a stateful shopping cart API using Beeceptor response templates. Read request data, persist state, retrieve JSON arrays to personalize user experiences, and simulate checkout workflows.",sidebar_label:"Build a Stateful Shopping Cart Using Beeceptor",sidebar_position:6,tags:["Tutorials","Response Templates","Stateful Mocking","JSON"],date:new Date("2026-07-10T00:00:00.000Z")},d,o={},l=[{value:"How the Shopping Cart Works",id:"how-the-shopping-cart-works",level:2},{value:"Before You Start",id:"before-you-start",level:2},{value:"Double Braces, Triple Braces, and <code>json</code>",id:"double-braces-triple-braces-and-json",level:3},{value:"Step 1 - Log In and Return a JWT",id:"step-1---log-in-and-return-a-jwt",level:2},{value:"Step 2 - Add a Product to the User&#39;s Cart",id:"step-2---add-a-product-to-the-users-cart",level:2},{value:"Step 3 - Retrieve the Saved Cart",id:"step-3---retrieve-the-saved-cart",level:2},{value:"Step 4 - Handle a Root-Level JSON Array",id:"step-4---handle-a-root-level-json-array",level:2},{value:"Step 5 - Update an Item&#39;s Quantity",id:"step-5---update-an-items-quantity",level:2},{value:"Step 6 - Complete Checkout and Clear the Cart",id:"step-6---complete-checkout-and-clear-the-cart",level:2},{value:"Common Formatting Mistakes",id:"common-formatting-mistakes",level:2},{value:"Returning an Object Inside Quotes",id:"returning-an-object-inside-quotes",level:3},{value:"Omitting <code>json</code> for Structured Data",id:"omitting-json-for-structured-data",level:3},{value:"Quoting Numbers and Booleans",id:"quoting-numbers-and-booleans",level:3},{value:"Let&#39;s Test",id:"lets-test",level:2},{value:"Recap",id:"recap",level:2}];function c(e){let s={a:"a",code:"code",em:"em",h2:"h2",h3:"h3",img:"img",li:"li",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,a.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(s.p,{children:"A real backend rarely returns the same response every time. It reads the incoming request, identifies the user, inspects the request body and past context, and builds a response from that information."}),"\n",(0,r.jsxs)(s.p,{children:["Beeceptor lets you build virtual APIs that behave the same way. Instead of returning static JSON, ",(0,r.jsx)(s.a,{href:"/parameterized-dynamic-response-payload-using-request-header/",children:"response templates"})," can read request bodies, headers, query parameters, path parameters, and JWT claims. ",(0,r.jsx)(s.a,{href:"/template-stateful-mocks/",children:"Stateful helpers"})," can also store data between requests, making the mock behave much more like a real API."]}),"\n",(0,r.jsx)(s.p,{children:"In this tutorial, you will build the backend logic for a shopping cart application without writing a backend. The virtual API will support a complete shopping flow:"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsx)(s.li,{children:"Log in and return a JWT."}),"\n",(0,r.jsx)(s.li,{children:"Add products to a cart scoped to the signed-in user."}),"\n",(0,r.jsx)(s.li,{children:"Retrieve the user's saved cart."}),"\n",(0,r.jsx)(s.li,{children:"Read a root-level JSON array for a bulk operation."}),"\n",(0,r.jsx)(s.li,{children:"Update an item's quantity using a path parameter."}),"\n",(0,r.jsx)(s.li,{children:"Complete checkout and clear the cart."}),"\n"]}),"\n",(0,r.jsx)(s.p,{children:"The companion shopping-cart demo code will be shared in a GitHub repository and linked from this tutorial."}),"\n",(0,r.jsx)(s.p,{children:(0,r.jsx)(s.img,{alt:"Customer login screen for the shopping cart demo",src:t(33171).A+"",width:"1440",height:"980"})}),"\n",(0,r.jsx)(s.p,{children:(0,r.jsx)(s.em,{children:"The demo starts with a customer login backed by a virtual API."})}),"\n",(0,r.jsx)(s.h2,{id:"how-the-shopping-cart-works",children:"How the Shopping Cart Works"}),"\n",(0,r.jsx)(s.p,{children:"The storefront signs in as a customer, adds products to a cart, changes item quantities, and checks out. The website behaves like a regular shopping application; Beeceptor provides its backend behavior through virtual API rules."}),"\n",(0,r.jsx)(s.p,{children:(0,r.jsx)(s.img,{alt:"Storefront page after login",src:t(71735).A+"",width:"1440",height:"980"})}),"\n",(0,r.jsx)(s.p,{children:(0,r.jsx)(s.em,{children:"A customer can browse products and manage a personalized cart."})}),"\n",(0,r.jsxs)(s.p,{children:["Every cart operation reads the ",(0,r.jsx)(s.code,{children:"user_id"})," claim from the JWT and uses it to build a state key. Two customers can call the same endpoint while their cart items remain stored in separate lists."]}),"\n",(0,r.jsx)(s.h2,{id:"before-you-start",children:"Before You Start"}),"\n",(0,r.jsx)(s.p,{children:"Beeceptor exposes a powerful template builder built on top of Handlebars. The quick reference below introduces the helpers used throughout the tutorial so you can recognize how each value enters a response."}),"\n",(0,r.jsxs)(s.table,{children:[(0,r.jsx)(s.thead,{children:(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.th,{children:"Data to read"}),(0,r.jsx)(s.th,{children:"Template"})]})}),(0,r.jsxs)(s.tbody,{children:[(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:"Request body field"}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"{{body 'item.sku'}}"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:"Nested body field"}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"{{body 'shipping_address.city'}}"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:"Root-array field"}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"{{body '0.sku'}}"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:"Whole request body"}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"{{{body}}}"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:"Request header"}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"{{header 'x-client' 'unknown'}}"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:"Query parameter"}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"{{queryParam 'currency' 'USD'}}"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:"Path parameter"}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"{{pathParam 'index'}}"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:"JWT claim"}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"{{jwtPayload (header 'authorization') 'user_id'}}"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:"JSON object"}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"{{{json (body 'item')}}}"})})]}),(0,r.jsxs)(s.tr,{children:[(0,r.jsx)(s.td,{children:"Stateful list"}),(0,r.jsx)(s.td,{children:(0,r.jsx)(s.code,{children:"{{{json (list 'get' 'cart:u_101')}}}"})})]})]})]}),"\n",(0,r.jsxs)(s.h3,{id:"double-braces-triple-braces-and-json",children:["Double Braces, Triple Braces, and ",(0,r.jsx)(s.code,{children:"json"})]}),"\n",(0,r.jsxs)(s.p,{children:["Use double braces, ",(0,r.jsx)(s.code,{children:"{{...}}"}),", when inserting a primitive value as text. This is the common choice for a string placed inside a JSON response:"]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-json",children:'{\n  "sku": "{{body \'item.sku\'}}"\n}\n'})}),"\n",(0,r.jsxs)(s.p,{children:["Use triple braces, ",(0,r.jsx)(s.code,{children:"{{{...}}}"}),", when the template must insert an unescaped value. Objects and arrays must also pass through the ",(0,r.jsx)(s.code,{children:"json"})," helper so they are serialized as valid JSON:"]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-json",children:"{\n  \"item\": {{{json (body 'item')}}},\n  \"items\": {{{json (list 'get' 'cart:u_101')}}}\n}\n"})}),"\n",(0,r.jsxs)(s.p,{children:["This distinction matters because JSON strings, objects, and arrays have different syntax. Double braces are suitable for text. Triple braces prevent Handlebars from escaping JSON syntax, while ",(0,r.jsx)(s.code,{children:"json"})," converts an object or array into that syntax."]}),"\n",(0,r.jsx)(s.h2,{id:"step-1---log-in-and-return-a-jwt",children:"Step 1 - Log In and Return a JWT"}),"\n",(0,r.jsxs)(s.p,{children:["Create a ",(0,r.jsx)(s.code,{children:"POST /auth/login"})," mocking rule and enable the templating engine. The request body contains the selected customer:"]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-json",children:'{\n  "user_id": "u_101",\n  "name": "Maya Wilson",\n  "email": "[email protected]"\n}\n'})}),"\n",(0,r.jsx)(s.p,{children:"Use the following response template. It reads the request fields and includes them in the JWT payload:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-json",children:'{\n  "access_token": "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0=.{{{base64 (json (object user_id=(body \'user_id\') name=(body \'name\') email=(body \'email\')))}}}.demo-signature",\n  "token_type": "Bearer",\n  "user": {\n    "user_id": "{{body \'user_id\'}}
1",\n    "name": "{{body \'name\'}}",\n    "email": "{{body \'email\'}}"\n  }\n}\n'})}),"\n",(0,r.jsxs)(s.p,{children:["Here, ",(0,r.jsx)(s.code,{children:"body"})," reads primitive fields from the incoming JSON. The ",(0,r.jsx)(s.code,{children:"object"})," helper builds the JWT payload, ",(0,r.jsx)(s.code,{children:"json"})," serializes it, and triple braces keep the serialized value unescaped before ",(0,r.jsx)(s.code,{children:"base64"})," encodes it. See the ",(0,r.jsxs)(s.a,{href:"/template-miscellaneous-operators/#jwtpayload",children:[(0,r.jsx)(s.code,{children:"jwtPayload"})," helper reference"]})," for more ways to extract complete payloads or individual claims from incoming tokens."]}),"\n",(0,r.jsx)(s.h2,{id:"step-2---add-a-product-to-the-users-cart",children:"Step 2 - Add a Product to the User's Cart"}),"\n",(0,r.jsxs)(s.p,{children:["When the customer clicks ",(0,r.jsx)(s.strong,{children:"Add to cart"}),", the frontend sends a nested product object:"]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-json",children:'{\n  "item": {\n    "sku": "SKU-CHAIR-01",\n    "name": "Ergo Chair",\n    "quantity": 1,\n    "price": 149.99,\n    "fulfillment": {\n      "city": "Austin",\n      "warehouse": "AUS-1"\n    }\n  }\n}\n'})}),"\n",(0,r.jsxs)(s.p,{children:["Create a ",(0,r.jsx)(s.code,{children:"POST /cart/items"})," rule and start its response template with this stateful operation:"]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-handlebars",children:"{{list 'push' (concat 'cart:' (jwtPayload (header 'authorization') 'user_id')) (body 'item')}}\n"})}),"\n",(0,r.jsx)(s.p,{children:"This line creates a user-specific cart bucket and pushes the incoming item into it:"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"header 'authorization'"})," reads the bearer token."]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"jwtPayload ... 'user_id'"})," extracts the signed-in customer's ID."]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"concat 'cart:' ..."})," builds a list key such as ",(0,r.jsx)(s.code,{children:"cart:u_101"}),"."]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"body 'item'"})," reads the complete nested product object."]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"list 'push'"})," appends that object to the list stored under the user-specific key."]}),"\n"]}),"\n",(0,r.jsxs)(s.p,{children:["The same expression produces a different key for each JWT user. That is what keeps Maya's cart separate from another customer's cart while both use ",(0,r.jsx)(s.code,{children:"POST /cart/items"}),"."]}),"\n",(0,r.jsx)(s.p,{children:"Complete the rule with this response:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-json",children:"{{list 'push' (concat 'cart:' (jwtPayload (header 'authorization') 'user_id')) (body 'item')}}\n{\n  \"message\": \"Item added\",\n  \"user_id\": \"{{jwtPayload (header 'authorization') 'user_id'}}\",\n  \"request_id\": \"{{header 'x-request-id' 'missing'}}\",\n  \"added_sku\": \"{{body 'item.sku'}}\",\n  \"added_item\": {{{json (body 'item')}}},\n  \"cart_size\": {{list 'size' (concat 'cart:' (jwtPayload (header 'authorization') 'user_id'))}},\n  \"cart\": {{{json (list 'get' (concat 'cart:' (jwtPayload (header 'authorization') 'user_id')))}}}\n}\n"})}),"\n",(0,r.jsx)(s.p,{children:"Notice how the output format determines the brace style:"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"{{body 'item.sku'}}"})," uses double braces because the SKU is text inside JSON quotes."]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"{{{json (body 'item')}}}"})," uses triple braces and ",(0,r.jsx)(s.code,{children:"json"})," because the item must remain an object."]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"{{{json (list 'get' ... )}}}"})," does the same for the entire cart array."]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"{{list 'size' ...}}"})," is unquoted because the response expects a JSON number."]}),"\n"]}),"\n",(0,r.jsx)(s.p,{children:(0,r.jsx)(s.img,{alt:"Cart drawer with quantity controls",src:t(34942).A
1+"",width:"1440",height:"980"})}),"\n",(0,r.jsx)(s.p,{children:(0,r.jsx)(s.em,{children:"The stored cart is returned to the storefront after an item is added."})}),"\n",(0,r.jsxs)(s.p,{children:["You can inspect the user-specific lists from Beeceptor's stateful data interface. Keys such as ",(0,r.jsx)(s.code,{children:"cart:u_101"})," make it easy to see how each JWT user receives an independent cart."]}),"\n",(0,r.jsx)(s.p,{children:(0,r.jsx)(s.img,{alt:"Beeceptor&#39;s stateful data Mangment Console",src:t(8553).A+"",width:"2716",height:"1156"})}),"\n",(0,r.jsx)(s.h2,{id:"step-3---retrieve-the-saved-cart",children:"Step 3 - Retrieve the Saved Cart"}),"\n",(0,r.jsxs)(s.p,{children:["Create a ",(0,r.jsx)(s.code,{children:"GET /cart"})," rule. The storefront can request ",(0,r.jsx)(s.code,{children:"/cart?currency=USD&includeDebug=true"}),", allowing this step to demonstrate both query parameters and persisted state:"]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-json",children:"{\n  \"user_id\": \"{{jwtPayload (header 'authorization') 'user_id'}}\",\n  \"currency\": \"{{queryParam 'currency' 'USD'}}\",\n  \"include_debug\": \"{{queryParam 'includeDebug' 'false'}}\",\n  \"cart_size\": {{list 'size' (concat 'cart:' (jwtPayload (header 'authorization') 'user_id'))}},\n  \"items\": {{{json (list 'get' (concat 'cart:' (jwtPayload (header 'authorization') 'user_id')))}}},\n  \"jwt_payload\": {{{json (jwtPayload (header 'authorization'))}}}\n}\n"})}),"\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.code,{children:"queryParam"})," accepts a fallback as its second argument. The response uses ",(0,r.jsx)(s.code,{children:"USD"})," when ",(0,r.jsx)(s.code,{children:"currency"})," is absent and ",(0,r.jsx)(s.code,{children:"false"})," when ",(0,r.jsx)(s.code,{children:"includeDebug"})," is absent. The ",(0,r.jsx)(s.code,{children:"list 'get'"})," call uses the same JWT-derived key created while adding an item, then ",(0,r.jsx)(s.code,{children:"json"})," serializes the stored list as a response array."]}),"\n",(0,r.jsx)(s.h2,{id:"step-4---handle-a-root-level-json-array",children:"Step 4 - Handle a Root-Level JSON Array"}),"\n",(0,r.jsxs)(s.p,{children:["Not every API receives an object. Bulk operations often send a JSON array directly as the request body. Beeceptor supports both index-based access and dynamic transformations; see ",(0,r.jsx)(s.a,{href:"/template-array-usage/",children:"Handling Arrays with Template Engine"})," for additional array patterns."]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-json",children:'[\n  {\n    "sku": "SKU-CHAIR-01",\n    "name": "Ergo Chair"\n  },\n  {\n    "sku": "SKU-BAG-01",\n    "name": "Travel Bag",\n    "fulfillment": {\n      "city": "Seattle",\n      "country": "United States"\n    }\n  }\n]\n'})}),"\n",(0,r.jsxs)(s.p,{children:["Instead of object paths such as ",(0,r.jsx)(s.code,{children:"item.sku"}),", use the array index as the first path segment. Create a ",(0,r.jsx)(s.code,{children:"POST /cart/items/preview-bulk"})," rule with this response:"]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-json",children:'{\n  "user_id": "{{jwtPayload (header \'authorization\') \'user_id\'}}",\n  "first_sku": "{{body \'0.sku\'}}",\n  "second_name": "{{body \'1.name\'}}",\n  "second_fulfillment_city": "{{body \'1.fulfillment.city\'}}",\n  "second_item": {{{json (body \'1\')}}},\n  "second_fulfillment": {{{json (body \'1.fulfillment\')}}},\n  "request_body": {{{body}}}\n}\n'})}),"\n",(0,r.jsx)(s.p,{children:"Each expression follows the shape of the incoming array:"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"{{body '0.sku'}}"})," reads the first product's SKU."]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"{{body '1.name'}}"})," reads the second product's name."]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"{{body '1.fulfillment.city'}}"})," reaches a nested field inside the second product."]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"{{{json (body '1')}}}"})," returns the entire second product as a JSON object."]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"{{{json (body '1.fulfillment')}}}"})," returns the nested fulfillment object."]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"{{{body}}}"})," reuses the complete request body when it is already valid raw JSON."]}),"\n"]}),"\n",(0,r.jsxs)(s.p,{children:["The object expressions use ",(0,r.jsx)(s.code,{children:"json"})," because they return structured values. Primitive fields such as ",(0,r.jsx)(s.code,{children:"sku"}),", ",(0,r.jsx)(s.code,{children:"name"}),", and ",(0,r.jsx)(s.code,{children:"city"})," can be inserted as text with double braces."]}),"\n",(0,r.jsx)(s.h2,{id:"step-5---update-an-items-quantity",children:"Step 5 - Update an Item's Quantity"}),"\n",(0,r.jsx)(s.p,{children:"When the customer changes a quantity, the storefront sends a request such as:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-http",children:"PATCH /cart/items/0\n"})}),"\n",(0,r.jsx)(s.p,{children:"The request body contains the updated item:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-json",children:'{\n  "item": {\n    "sku": "SKU-CHAIR-01",\n    "name": "Ergo Chair",\n    "quantity": 2,\n    "price": 149.99,\n    "fulfillment": {\n      "city": "Austin",\n      "warehouse": "AUS-1"\n    }\n  }\n}\n'})}),"\n",(0,r.jsxs)(s.p,{children:["Create a ",(0,r.jsx)(s.code,{children:"PATCH /cart/items/:index"})," rule using a path-template condition, then use ",(0,r.jsx)(s.code,{children:"pathParam 'index'"})," in the response template:"]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-json",children:"{{list 'update' (concat 'cart:' (jwtPayload (header 'authorization') 'user_id')) (add (pathParam 'index') 0) (body 'item')}}
1\n{\n  \"message\": \"Cart item updated\",\n  \"user_id\": \"{{jwtPayload (header 'authorization') 'user_id'}}\",\n  \"updated_index\": {{pathParam 'index'}},\n  \"updated_item\": {{{json (body 'item')}}},\n  \"cart\": {{{json (list 'get' (concat 'cart:' (jwtPayload (header 'authorization') 'user_id')))}}}\n}\n"})}),"\n",(0,r.jsxs)(s.p,{children:[(0,r.jsx)(s.code,{children:"pathParam 'index'"})," reads ",(0,r.jsx)(s.code,{children:"0"})," from the URL as a string. The list update operation currently requires a numeric index, so ",(0,r.jsx)(s.code,{children:"(add (pathParam 'index') 0)"})," converts that numeric string to a number before passing it to ",(0,r.jsx)(s.code,{children:"list 'update'"}),"."]}),"\n",(0,r.jsx)(s.p,{children:(0,r.jsx)(s.img,{alt:"Cart drawer after updating item quantity",src:t(48473).A+"",width:"1440",height:"980"})}),"\n",(0,r.jsx)(s.p,{children:(0,r.jsx)(s.em,{children:"The path parameter identifies which stored item the quantity control updates."})}),"\n",(0,r.jsx)(s.h2,{id:"step-6---complete-checkout-and-clear-the-cart",children:"Step 6 - Complete Checkout and Clear the Cart"}),"\n",(0,r.jsx)(s.p,{children:"The checkout request includes a client header and a nested JSON body:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-http",children:"POST /checkout\nX-Client: web-demo\n"})}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-json",children:'{\n  "payment": {\n    "method": "card",\n    "last4": "4242"\n  },\n  "shipping_address": {\n    "line1": "500 Congress Avenue",\n    "city": "Austin",\n    "country": "United States"\n  }\n}\n'})}),"\n",(0,r.jsxs)(s.p,{children:["Create a ",(0,r.jsx)(s.code,{children:"POST /checkout"})," rule with the following response template:"]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-json",children:"{{step-counter 'inc' (concat 'order:' (jwtPayload (header 'authorization') 'user_id')) 1}}\n{\n  \"order_id\": \"ORD-{{jwtPayload (header 'authorization') 'user_id'}}-{{step-counter 'get' (concat 'order:' (jwtPayload (header 'authorization') 'user_id'))}}\",\n  \"status\": \"confirmed\",\n  \"user_id\": \"{{jwtPayload (header 'authorization') 'user_id'}}\",\n  \"client\": \"{{header 'x-client' 'unknown'}}\",\n  \"payment\": {{{json (body 'payment')}}},\n  \"shipping_city\": \"{{body 'shipping_address.city'}}\",\n  \"shipping_address\": {{{json (body 'shipping_address')}}},\n  \"items\": {{{json (list 'get' (concat 'cart:' (jwtPayload (header 'authorization') 'user_id')))}}},\n  \"cart_cleared\": true\n{{list 'reset' (concat 'cart:' (jwtPayload (header 'authorization') 'user_id'))}}\n}\n"})}),"\n",(0,r.jsx)(s.p,{children:"This final rule combines the patterns used throughout the tutorial:"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"header 'x-client' 'unknown'"})," reads a request header with a fallback."]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"body 'shipping_address.city'"})," reads a nested primitive."]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"json (body 'shipping_address')"})," serializes a nested object."]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"step-counter"})," creates a per-user order sequence."]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"list 'get'"})," returns the user's current cart in the order response."]}),"\n",(0,r.jsxs)(s.li,{children:[(0,r.jsx)(s.code,{children:"list 'reset'"})," clears only that user's cart after checkout."]}),"\n"]}),"\n",(0,r.jsx)(s.h2,{id:"common-formatting-mistakes",children:"Common Formatting Mistakes"}),"\n",(0,r.jsx)(s.h3,{id:"returning-an-object-inside-quotes",children:"Returning an Object Inside Quotes"}),"\n",(0,r.jsx)(s.p,{children:"An object helper placed inside quotes becomes text rather than a JSON object:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-json",children:'{\n  "item": "{{body \'item\'}}"\n}\n'})}),"\n",(0,r.jsx)(s.p,{children:"Serialize the object and insert it without escaping:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-json",children:"{\n  \"item\": {{{json (body 'item')}}}\n}\n"})}),"\n",(0,r.jsxs)(s.h3,{id:"omitting-json-for-structured-data",children:["Omitting ",(0,r.jsx)(s.code,{children:"json"})," for Structured Data"]}),"\n",(0,r.jsxs)(s.p,{children:["Objects and arrays should pass through ",(0,r.jsx)(s.code,{children:"json"})," before being embedded in a response:"]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-json",children:"{\n  \"fulfillment\": {{{json (body '1.fulfillment')}}},\n  \"cart\": {{{json (list 'get' (concat 'cart:' (jwtPayload (header 'authorization') 'user_id')))}}}\n}\n"})}),"\n",(0,r.jsx)(s.h3,{id:"quoting-numbers-and-booleans",children:"Quoting Numbers and Booleans"}),"\n",(0,r.jsx)(s.p,{children:"Values placed inside quotes become JSON strings. Leave numeric or boolean expressions unquoted when the client expects their native types:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-json",children:"{\n  \"cart_size\": {{list 'size' (concat 'cart:' (jwtPayload (header 'authorization') 'user_id'))}},\n  \"available\": true\n}\n"})}),"\n",(0,r.jsx)(s.h2,{id:"lets-test",children:"Let's Test"}),"\n",(0,r.jsxs)(s.p,{children:["Replace ",(0,r.jsx)(s.code,{children:"shopping-cart"})," with your Beeceptor endpoint name:"]}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-bash",children:'BASE_URL="https://shopping-cart.proxy.beeceptor.com"\n'})}),"\n",(0,r.jsx)(s.p,{children:"First, log in:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-bash",children:'curl -X POST "$BASE_URL/auth/login" \\\n  -H "Content-Type: application/json" \\\n  -d \'{"user_id":"u_101","name":"Maya Wilson","email":"[email protected]"}\'\n'})}),"\n",(0,r.jsx)(s.p,{children:"Copy the returned token and use it to add an item:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-bash",children:'curl -X POST "$BASE_URL/cart/items" \\\n  -H "Authorization: Bearer <access_token>" \\\n  -H "Content-Type: application/json" \\\n  -d \'{"item":{"sku":"SKU-CHAIR-01","name":"Ergo Chair","quantity":1,"price":149.99,"fulfillment":{"city":"Austin","warehouse":"AUS-1"}}}\'\n'})}),"\n",(0,r.jsx)(s.p,{children:"Retrieve the saved cart and read query parameters:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-bash",children:'curl "$BASE_URL/cart?currency=USD&includeDebug=true" \\\n  -H "Authorization: Bearer <access_token>"\n'})}),"\n",(0,r.jsx)(s.p,{children:"Update the first item's quantity:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-bash",children:'curl -X PATCH "$BASE_URL/cart/items/0" \\\n  -H "Authorization: Bearer <access_token>" \\\n  -H "Content-Type: application/json" \\\n  -d \'{"item":{"sku":"SKU-CHAIR-01","name":"Ergo Chair","quantity":2,"price":149.99,"fulfillment":{"city":"Austin","warehouse":"AUS-1"}}}\'\n'})}),"\n",(0,r.jsx)(s.p,{children:"Finally, check out and clear the user's cart:"}),"\n",(0,r.jsx)(s.pre,{children:(0,r.jsx)(s.code,{className:"language-bash",children:'curl -X POST "$BASE_URL/checkout" \\\n  -H "Authorization: Bearer <access_token>" \\\n  -H "X-Client: web-demo" \\\n  -H "Content-Type: application/json" \\\n  -d \'{"payment":{"method":"card","last4":"4242"},"shipping_address":{"line1":"500 Congress Avenue","city":"Austin","country":"United States"}}\'\n'})}),"\n",(0,r.jsx)(s.h2,{id:"recap",children:"Recap"}),"\n",(0,r.jsx)(s.p,{children:"You built a stateful shopping cart virtual API that reads request data and formats valid JSON responses. Along the way, you learned to:"}),"\n",(0,r.jsxs)(s.ul,{children:["\n",(0,r.jsxs)(s.li,{children:["Read primitive and nested fields with ",(0,r.jsx)(s.code,{children:"body"}),"."]}),"\n",(0,r.jsx)(s.li,{children:"Access root-level array elements by numeric path segments."}),"\n",(0,r.jsx)(s.li,{children:"Read headers, query parameters, path parameters, and JWT claims."}),"\n",(0,r.jsxs)(s.li,{children:["Use ",(0,r.jsx)(s.code,{children:"{{...}}"})," for text and unquoted primitive expressions."]}),"\n",(0,r.jsxs)(s.li,{children:["Use ",(0,r.jsx)(s.code,{children:"{{{...}}}"})," with ",(0,r.jsx)(s.code,{children:"json"})," for objects and arrays."]}),"\n",(0,r.jsx)(s.li,{children:"Derive user-specific state keys from JWT claims."}),"\n",(0,r.jsx)(s.li,{children:"Persist, retrieve, update, and reset cart data with stateful helpers."}),"\n"]}),"\n",(0,r.jsx)(s.p,{children:"These patterns let a Beeceptor virtual API preserve context and respond like a real backend while the production service is still being designed, built, or tested."})]})}function h(e={}){let{wrapper:s}={...(0,a.R)(),...e.components};return s?(0,r.jsx)(s,{...e,children:(0,r.jsx)(c,{...e})}):c(e)}},33171(e,s,t){t.d(s,{A:()=>n});let n=t.p+"assets/images/01-login-8bbec5f1f577e21954e57d2cde0d3e3a.png"},71735(e,s,t){t.d(s,{A:()=>n});let n=t.p+"assets/images/02-storefront-4fdb92a64b81d4f9f54a2151e34e1b5b.png"},34942(e,s,t){t.d(s,{A:()=>n});let n=t.p+"assets/images/03-cart-update-8b4f992bc619fc4c5a684ba2513c1c0a.png"},48473(e,s,t){t.d(s,{A:()=>n});let n=t.p+"assets/images/04-updated-cart-7bb508194a94daa1d38575add026dbeb.png"},8553(e,s,t){t.d(s,{A:()=>n});let n=t.p+"assets/images/05-cart-status-983538d603f5fd533d869628d9e8a509.png"},28453(e,s,t){t.d(s,{R:()=>i,x:()=>d});var n=t(96540);let r={},a=n.createContext(r);function i(e){let s=n.useContext(a);return n.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function d(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:i(e.components),n.createElement(a.Provider,{value:s},e.children)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.