Building a Stateful Movie Booking Flow
Testing multi-step user journeys needs state persistence across APIs. In reality, backend systems store this state in sessions or databases. But in a mock server, requests are independent unless you add a mechanism to tie them together. Beeceptor provides stateful mocks to bridge this gap.
Stateful mocks use three helpers:
- Data Store: key-value pairs to hold things like the selected movie.
- List: ordered collections, great for seat selections or shopping carts.
- Step Counter: numeric tracker, often used for IDs or sequence numbers.
In this tutorial, we’ll walk through simulating a movie ticket booking flow using Beeceptor's stateful mocks. Let's assume that the backend team has shared the API design/contracts for movie booking management already. However these APIs aren't ready yet. What you need is a mock server so that you can showcase this whole integration without waiting for your backend team or the real service deployment. These API calls are /select-movie
, /select-seats
, /booking-summary
, and /reset-booking
. We’ll configure each route with templates that persist and retrieve data from Beeceptor’s in-memory store.
Step 1 – Creating Mock Server
First, open Beeceptor and create a Beeceptor endpoint. We call this api-service
.
https://api-service.proxy.beeceptor.com
On the dashboard, go to Mocking Rules. Next following the step below to create mock behaviors.
Step 2 – API: Selecting a movie
When the user chooses a movie in the app, the frontend sends a POST /select-movie
with JSON like:
{
"movieId": "M001"
}
In our template, we extract this movieId
and save it to the data store under a key called selectedMovie
.
{{data-store 'set' 'selectedMovie' (body 'movieId')}}
{
"message": "Movie selected",
"movieId": "{{body 'movieId'}}"
}
Here’s what happens line by line:
data-store 'set'
: takes the incoming movieId and stores it.- The JSON body echoes back the same ID so the frontend knows the operation succeeded.
Make sure to check Enable Templating for each rule, otherwise the {{data-store}}
and {{list}}
helpers won’t work.
Step 3 – API: Selecting seats
When a user clicks seats in the UI, each action sends a POST /select-seats
with JSON like:
{
"seatNumber": "A1"
}
We append each seatNumber to a list called selectedSeats.
{{list 'push' 'selectedSeats' (body 'seatNumber')}}
{
"message": "Seat added",
"selectedSeats": [{{list 'get' 'selectedSeats'}}]
}
Explanation:
- The first line pushes the seat into the list. Beeceptor allows duplicates, so if the frontend retries, the same seat can appear twice unless you add extra validation.
list 'get'
retrieves the full list, so the frontend always has the updated array of chosen seats.
Step 4 – API: Booking summary
At the summary step, the UI calls GET /booking-summary
. This should return both the movie ID and the seat list.
Response Template:
{
"movieId": "{{data-store 'get' 'selectedMovie'}}",
"seats": [{{list 'get' 'selectedSeats'}}]
}
Breaking it down:
data-store 'get'
: fetches the movie ID that was set in Step 2.list 'get'
: fetches all seats from Step 3.- This combines the session state into a single payload that the frontend can render.
Step 5 – API: Resetting the booking
To allow fresh test runs, the HTML page also calls GET /reset-booking
. This clears the stored movie and seat list.
{{data-store 'set' 'selectedMovie' ''}}
{{list 'reset' 'selectedSeats'}}
{
"message": "Booking session cleared."
}
This resets the state entirely, so the next run starts clean.
Testing All APIs
Once these rules are configured, open your provided movie-booking.html
or your frontend's client URL in a browser. Each button click will make calls to Beeceptor, and you’ll see state being preserved across API calls.
curl -X POST https://api-service.proxy.beeceptor.com/select-movie \
-H 'Content-Type: application/json' -d '{"movieId":"M001"}'
curl -X POST https://api-service.proxy.beeceptor.com/select-seats \
-H 'Content-Type: application/json' -d '{"seatNumber":"A1"}'
curl https://api-service.proxy.beeceptor.com/booking-summary
curl https://api-service.proxy.beeceptor.com/reset-booking
Advanced: Per session state
Once you’ve mastered the above state persistence, Beeceptor’s lets you explore advanced tricks to make mocks feel even closer to real backends. In the following you can scope data to a specific user by deriving the store key from a cookie called sessionId
(or header). For example, storing selected seats under a session-specific key:
{{list 'push' (concat 'selectedSeats_' (cookie 'sessionId')) (body 'seatNumber')}}
{
"session": "{{cookie 'sessionId'}}",
"selectedSeats": [{{list 'get' (concat 'selectedSeats_' (cookie 'sessionId'))}}]
}
Now each user session has its own isolated seat list.
Wrapping it up
With a few rules, you’ve built a mock backend that preserves state across requests and powers a real user journey. The pattern extends well beyond movies:
- E-commerce checkout: add products with
POST /add-to-cart
, track them in a list, and return totals at GET /cart-summary. - Onboarding wizards: persist profile details across multiple screens and verify the review step shows everything combined.
- Form builders: save draft data in a data store, progressively enrich it, then submit the final payload.
- Banking or payments: simulate transfer initiation, OTP verification, and final confirmation with state carried across calls.
Frontend teams can now test flows without waiting on backend APIs, validating navigation, UI updates, and error handling under realistic conditions. Beeceptor’s data store, lists, and counters lets you test complex interactions early and uncover issues before backend services are even ready.