Build a Point-of Sale App with Serialized
Since POS systems are the backbone of many retail and food businesses, I was intrigued by the idea of building one. In this article, we’ll dive into building a POS web application that uses React, Express, and Serialized.


When we think of technology, we often don’t think of day-to-day businesses like restaurants, kiosks, and shops. However, technology is being used in retail and food service every day! The main technological intersection between these types of businesses is a POS (which stands for “point-of-sale") system. It’s this program that makes sure you get those tacos you were craving from your favorite restaurant, that sweater you’ve been eyeballing on Poshmark, and that new iPhone on the Apple website. They allow employees to ring up and itemize orders too, providing the primary means of communication for orders across the whole business.
Since POS systems are the backbone of many retail and food businesses, I was intrigued by the idea of building one. In this article, we’ll dive into building a POS web application that uses React, Express, and Serialized.
What We’re Building
Our POS system will use React for the frontend, Express for the backend, and Serialized to create and store orders, as well as continuously add items to orders.
Serialized is a cloud-hosted API engine for building event-driven systems — it helps us easily capture the comprehensive timeline and history of events and aggregate them into related groups. In relation to our POS system, we’ll be using Serialized to keep track of events (customers ordering items) and aggregate them into related groups (customers’ orders) .
Below is a diagram of what the user flow will look like for the application:
The three main functionalities we’ll focus on in this tutorial are:
- creating new orders,
- adding items to existing orders, and
- marking orders as completed.
These three scenarios will capture the use cases our very basic POS system. The final product will look like this:
Getting Started
Before we get started building, make sure you set up the following:
- Node: To check if you have Node installed already, you can run
node -v
in your command line. If no version pops up, you’ll need to install it - you can find installation directions for your machine here. - npx:
npx
is a package runner for Node packages that allows you to execute packages from the npm registry without needing to install it. To check if you have it installed (typically comes with npm, which comes with Node), you can runnpx -v
. If no version pops up, you can installnpx
using the instructions here. - Serialized: To use the Serialized API, you’ll need to create an account. Once you
create an account, it’ll prompt you to also create a project, which is also required to start building with the API.
You can name your project whatever you’d like — I went with
POS App
. You can learn more about projects in Serialized here.
If you’d prefer to walk through code rather than build, I’ve got you! You can view the GitHub repository for this
project here. All instructions for running the project are
available in the repository’s README.md
in the root directory. (Tip: the GitHub repo is also a great source of
guidance if you get stuck while building alongside the tutorial!)
Project Setup
The setup for the project is based on this tutorial from freeCodeCamp .
- To start, initialize the project directory on your machine in your location of choice by running
mkdir pos-app
or creating apos-app
folder manually.cd
into it in you Terminal and run
npx create-react-app client
This will create a folder named client
where your application’s frontend will live.
- Once the
client
folder has been created, run the following commands to enter the newly createdclient
folder, and then start the frontend server:
cd client npm start
If your project has been set up correctly, you should see the default React app in your browser
at http://localhost:3000
:
- If your frontend launched successfully, it’s time to now set up the backend! Terminate the frontend server by running
CTRL + C. Then, use the command
cd ../
from the client folder to switch back into your project’s root directory. Then, run the following commands to generate an Express application in a folder called api and start up the backend:
npx express-generator api cd api npm install npm start
If your backend was set up correctly, you should see this view after running npm start
:
You can learn more about the express-generator
package used to set up the
backend here.
- At this point, both the frontend and backend are wired up to
localhost:3000
. Since you’ll need to run both servers at the same time while developing the app, you’ll need to change the port the backend runs on to avoid a port collision. To do this, navigate to thebin/www
file in theapi
directory. Update line 15 so its default value now points to port 9000. The line will look like this once updated:
var port = normalizePort(process.env.PORT || '9000');
Now, when running npm start
in the api folder to start up the backend, you’ll be able to see the launched Express
server at localhost:9000
.
Setting up Serialized
- In order to use Serialized with the application that was set up in the steps above, you can install the Serialized
client for Javascript and Typescript. Since the Serialized API will be called in the Express backend, run the
following command to install the client in your
api
directory:
npm install @serialized/serialized-client
- Once the client has been installed, create a
.env
file in theapi
directory to set up environment variables for the Serialized API Keys that will be passed into the client to access your account information. Your.env
file will contain these two environment variables:
SERIALIZED_ACCESS_KEY= SERIALIZED_SECRET_ACCESS_KEY=
To find the SERIALIZED_ACCESS_KEY
and SERIALIZED_SECRET_ACCESS_KEY
values, go to Settings > API Keys in
your Serialized dashboard for the project you created and set the environment variables to
the corresponding values.
Create New Orders
Now that the Serialized API and authorization has been configured, you can make your first call from your application to the API! In this section you’ll focus on our first use case of the Serialized Aggregates API to create a new order in our POS system.
- To get started, create an
order.js
file within theapi
directory. This file will be the scaffolding for defining the concept of an “order" to Serialized. It’s also where you will create or add items to orders, as well as other logic and event handlers for triggering our application’s functionality.
Paste the following code into theorder.js
file:
const { DomainEvent } = require("@serialized/serialized-client");class Order { get aggregateType() { return "order"; } constructor(state) { this.orderId = state.orderId; this.items = state.items; this.total = state.total; this.completed = state.completed; } createOrder(orderId) { if (!orderId || orderId.length !== 36) throw "Invalid orderId"; return [DomainEvent.create(new OrderCreated(orderId))]; } get eventHandlers() { return { OrderCreated(state, event) { console.log("Handling OrderCreated", event); return OrderState.newState(event.orderId).withOrderId(event.orderId); }, }; }}class OrderCreated { constructor(orderId) { this.orderId = orderId; }}class OrderState { constructor({ orderId, items = [], total = 0.0, completed = false }) { this.orderId = orderId; this.items = items; this.total = total; this.completed = completed; } static newState(orderId) { return new OrderState({ orderId }); } withOrderId(orderId) { return Object.assign({}, this, { orderId }); }}module.exports = { Order };
To walk through this file, let’s break it down class by class:
- Order: This class is a representation of an actual order object. The Order object is defined as an Aggregate in
Serialized, meaning that it is a process that consists of Events, which will be actions that happen to a particular
Order object. In this tutorial, these events would be creating new orders, adding an item to an order, and completing
the order.
- As indicated in the Order class’s constructor, declaring a new Order instance will require a
state
object representing the order and its current stats to be passed in. This is because each Aggregate is made up of Events, and they’re responsible for updating the state of the whole order as they get triggered. - Next, a
createOrder()
function is initialized — this will check if a givenorderId
exists and matches the 36-character UUID format specified for order IDs. Then it’ll initialize our new order-creation event with a call toDomainEvent.create()
. - Finally, an
eventHandlers()
function is declared, which takes in an Order’s current state and the event that happened to the order.- At this point in the tutorial, only an
OrderCreated
event handler has been returned for now, but there will be additional ones added for the other event types. Event handlers will log an event in the console and use theOrderState
object to keep track of the Order’s state.
- At this point in the tutorial, only an
- As indicated in the Order class’s constructor, declaring a new Order instance will require a
- OrderCreated: This class represents an event type — in this scenario, it’s that a new order was created. Every new
event added will require a new class that determines what information the event passes to the API. The class name
should match the event handler it corresponds to (in this case,
OrderCreated
. To create a new order, the only property required is anorderId
, so that is the only property declared in this class. - OrderState: This class defines an order’s current state and keeps track of it as it changes so it can be passed in
as events to the Order object, which will send the events to Serialize as they are triggered. Remember that a change
in state could be anything from adding new items to the order to marking it as completed — the latter of which is
denoted by the
OrderState
’scompleted
property being set totrue
.
- Once your
order.js
file is set up, add in anorder-client.js
file in the same directory. This file will act as a client that wires up authentication for the Serialized Aggregates API with the functionality written inorder.js
. Paste in the following code to theorder-client.js
file:
const { Order } = require("./order");const handleError = async function (handler) { try { await handler(); } catch (error) { throw new Error("Failed to process command: " + error); }};class OrderClient { constructor(serializedClient) { this.client = serializedClient.aggregateClient(Order); } async createOrder(orderId) { await handleError( async () => await this.client.create(orderId, (order) => { return order.createOrder(orderId); }) ); }}module.exports = OrderClient;
The file imports the Order
class from the previous order.js
file. Then, an error handler is initialized to handle
generic API request logic of calling a particular function and catching and surfacing any potential errors.
Additionally, an OrderClient
class is declared. This class assumes an authenticated instance of Serialized’s general
authentication API client is being passed in (serializedClient
), and it uses this to specifically initialize an
instance of the client’s Aggregates API client using the aggregateClient()
function.
- Once
order.js
andorder-client.js
have been set up, you can create a route that will initialize an authenticated Serialized API client and make the needed API requests callable from the frontend. Go to theapi/routes
directory and create a file calledorders.js
with the following code inside:
var express = require("express");require("dotenv").config();var router = express.Router();const { Serialized } = require("@serialized/serialized-client");const OrderClient = require("../order-client");const serializedClient = Serialized.create({ accessKey: process.env.SERIALIZED_ACCESS_KEY, secretAccessKey: process.env.SERIALIZED_SECRET_ACCESS_KEY,});const orderClient = new OrderClient(serializedClient);router.post("/create", async function (req, res, next) { const { orderId } = req.body; console.dir(req.body); try { var response = await orderClient.createOrder(orderId); res.send(response); } catch (error) { console.log(error); res.status(400).json({ error: error }); }});module.exports = router;
The above code initializes an authenticated instance of the Serialized client using your account’s access keys, creates
a new instance of the OrderClient
defined in order-client.js
using this Serialized client, and then calls a function
on that OrderClient
instance to create a new order based on the information that was passed in. Then, a /create
POST route is declared. This route that takes in orderId
in the request body. Using the OrderClient
instance
declared at the top of the file, it then calls the createOrder()
function from the order-client.js
file and passes
in the orderId
.
- Now that the
orders.js
route has been created, it needs to be added to theapp.js
in theapi
directory so it can be called within the app. Add an initialization for anordersRouter
variable on line 9 inapi/app.js
:
var ordersRouter = require("./routes/orders");
Then, in line 24 of api/app.js, add in an app.use()
declaration for the ordersRouter to point an /orders route to the
endpoints in that file:
app.use("/orders", ordersRouter);
Now that this route has been added in, we can POST to the /orders/create
endpoint on localhost:9000
, to create a new
order!
Wiring up our React Frontend
Now that the API routes have been configured on the Express side, let’s call it from the React frontend! We can set up
the frontend application to make an API call to the newly created /orders/create
route so we can make an order from
the frontend.
- Browsers often enforce a same-origin policy for requests, resulting in
CORS (Cross-Origin Resource Policy) errors in the event
that requests on a certain domain are made from a different origin domain. This example uses
localhost:3000
for the frontend while retrieving information from alocalhost:9000
endpoint from our Express backend — this difference in URLs will potentially create a CORS error, since the browser could say that violates the same-origin policy. To prevent CORS errors in your app once the frontend and backend are wired up, install the CORS package inapi
with the following command:
npm install --save cors
- In
api/app.js
, add the following on line 6 to add in the CORS package that was just installed to the backend:
var cors = require("cors");
Then at line 23, add the following line to instruct your Express app to use the CORS package:
npm install --save cors
It might be worth checking api/app.js
against the GitHub repo at this point, just to make sure
everything is set up right.
- In the
client
directory, create a new folder insidesrc
calledcomponents
and initialize a file calledPOSHome.js
:
import React from "react";
export default function POSHome() {
async function createOrder() {
var generatedOrderId = crypto.randomUUID();
var data = {orderId: generatedOrderId};
var order = await fetch("http://localhost:9000/orders/create", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify(data),
});
}
return (
<div>
<h1>POS System ☕️</h1>
<div>
<button onClick={createOrder}>Create Order</button>
</div>
</div>
);
}
This file declares a functional component called POSHome
(which is where the homepage of the POS system will live).
On this page, there will be a button that, when clicked, calls createOrder()
. That function uses crypto.randomUUID()
to generate a UUID that will fit the standards the backend is expecting, shoves it all into the data
object, and sends
it off to our new /orders/create
endpoint.
- Replace
client/src/App.js
with the following code so that thePOSHome
component is being passed in to the main application and is visible from the main page:
import "./App.css";
import POSHome from "./components/POSHome";
function App() {
return (
<div className="App">
<POSHome/>
</div>
);
}
export default App;
- Open a new window or tab in the Terminal so that you have two tabs or windows open. In one tab, run
npm start
in the api folder. In another tab, runnpm start
in theclient
folder. Oncelocalhost:3000
launches the frontend, you’ll see the following screen:
Click the Create Order button and then go to your Serialized dashboard for your project and
go to the Data Explorer page. You should see an entry for a new order — the one we just created on page load from the
POSHome frontend component calling the /orders/create
endpoint:
If you check the Terminal tab or window where you’re running the api
server, you’ll also see something like the
following:
OPTIONS /orders/create 204 0.236 ms - 0 { orderId: 'd3ce8600-9e71-4417-9726-ab3b9056df48' } POST /orders/create 200719.752 ms - -
This is an event log from the backend endpoint recording the instance of the new order being created. Any console.log
statements made from the backend will also show up here.
Integrating our functionality into our application
Now that you’ve taken a dive into the frontend code, let’s lay out the remaining flow for creating, adding items, and then completing an order.
- Let’s start by initializing a dataset that will represent the items you’ll be selling in your POS. In
client/src
, create a folder calleddata
and add in anitems.json
file. Within the file, set up something like this:
{ "items": [ { "name": "Tea", "price": 3.99 }, { "name": "Coffee", "price": 4.99 }, { "name": "Bagel", "price": 2.50 } ]}
Here we added some inventory items to the items
property array, each with a name
and price
property.
- Now that data has been added for what items are sold in the POS system, it needs to be surfaced in a view. This will
require a new component that is shown only when the Create Order button added in the last step is clicked.
In
client/src/components
, add anItemDisplay.js
file for a new checkout flow component. Here’s what that might look like:
import React from "react";export default function ItemDisplay (props) { var data = require("../data/items.json"); return ( <div> <div> {data.items.map((item, index) => { return ( <button key={index}> {item.name} </button> ); })} </div> </div> );}
Within the ItemDisplay
component, the data from items.json
is imported into the data
variable. Then, in
the return
of the component, each item in data
is iterated through and replaced with a button carrying that item’s
name as a label.
- Now, let’s update
client/src/components/POSHome.js
so that when an order is created, it’ll display theItemDisplay
component. We’ll use state variables for that — it’s great for conditionally rendering components. To start, update theimport
line at the top ofPOSHome.js
so it imports theuseState
hook too. While we’re there, bring in theItemDisplay
component from earlier.
import React, { useState } from "react"; import ItemDisplay from "./ItemDisplay";
- The
useState
hook will initialize a state variable for us and give us a way to update it in the future. Let’s start withstartedOrder
— this will keep track of whether an order has been started, and if so, it will display theItemDisplay
component. The variable will be initialized on line 5 with an initial value offalse
using the following:
const [startedOrder, setStartedOrder] = useState(false);
- Next, update your
return()
function in yourPOSHome
component so that it looks like the following:
return ( <div> <h1>POS System ☕️</h1> {!startedOrder && ( <div> <button onClick={createOrder}>Create Order</button> </div> )} {startedOrder && ( <ItemDisplay /> )} </div>);
In the above, JSX is being used to conditionally render certain elements depending on the value of the startedOrder
state variable. The logic implement here says: “If it’s false, render the Create Order button. If it’s true, render
the ItemDisplay
component."
- The final piece of this is setting
startedOrder
totrue
when an order is created. This can be done in thecreateOrder()
function above. Add the following block inside the function on line 15:
// if order was successful if (order.status === 200) { setStartedOrder(true); setOrderId(generatedOrderId); }
- Now it’s time to test the flow! Load up the frontend and backend of your application by running
npm start
in both theapi
andclient
directories in two different Terminal tabs or windows. Once the client has loaded, you should see your application appear inlocalhost:3000
. Click the Create Order button, and you should see your items appear as buttons on the page like in the screenshot below. This page, showing theItemDisplay
component, is where you’ll be able to select your items and add them to your order, which will be added in the section below.
Adding Items to Orders
Now we’re showing the available items, we need to be able to add those items to the running order.
To get started, let’s first jump into the backend.
- In
/client/api/order.js
, add in anItemAdded
event class under where theOrderCreated
class is declared:
class ItemAdded { constructor(orderId, itemName, itemPrice) { this.orderId = orderId; this.itemName = itemName;this.itemPrice = itemPrice; } }
This declares a class for a new event, ItemAdded
, that will take in an orderId
, itemName
, and itemPrice
.
- Add an
itemAdded()
function to yourOrder
class by adding the following code at line 19:
addItem(itemName, itemPrice) { if (this.completed)throw "List cannot be changed since it has been completed";return [DomainEvent.create(new ItemAdded(this.orderId, itemName, itemPrice))]; }
This function will first check if an order is completed - if it is, it’ll throw an error, as new items cannot be added.
If it isn’t, it’ll pull the orderId
directly from the Order object instance and take in an itemName
and itemPrice
to log an event instance of what item was added in to the order.
- In the
Order
class, add a new event handler for an item to be added:
ItemAdded(state, event) { console.log("Handling ItemAdded", event); return new Order(state).addItem({ orderId:event.orderId, itemName: event.itemName, itemPrice: event.itemPrice }); },
- Add the following inside the
OrderState
class at line 64:
addItem(itemName, itemPrice) { return Object.assign({}, this, { items: this.items.unshift({itemName: itemName,itemPrice: itemPrice}) }); }
The above code will update the items
array property of the OrderState
object so that the new item is pushed onto the
array.
At this point, it’s probably a good idea to match your order.js
against the GitHub repo to make sure it lines up.
- Once
api/order.js
has been updated, jump into theorder-client.js
file to add anaddItem()
function that will query theaddItem()
logic that was just added. Paste the following inside theOrderClient
class at line 24:
async addItem(orderId, itemName) { await handleError(async () =>await this.client.update(orderId, (order) => { return order.addItem(itemName); })); }
- Finally, add a route in
api/routes/orders.js
so that the functionality to add an item to an order can be called from the frontend. Add this code on line 24:
router.post("/add-item", async function (req, res, next) { const { orderId, itemName, itemPrice } = req.body;console.dir(req.body); try { var response = await orderClient.addItem(orderId, itemName, itemPrice); res.send(response);} catch (error) { console.log(error); res.status(400).json({ error: error }); } });
The above request will create an endpoint at /orders/add-item
that takes in an orderId
, itemName
, and itemPrice
in its request body to add an item and take note of its properties when it’s added to an order of a certain orderId
.
Consuming the endpoint we just made
Now that the backend is complete, let’s call this endpoint in the frontend! When an item button is selected in
the ItemDisplay
component, it should trigger the /orders/add-item
endpoint and also display an itemized receipt and
total order amount of items added so far in the order.
- To start, go to
/client/src/POSHome.js
. Since the/add-item
request takes in anorderId
, we need to pass it in to theItemDisplay
component to make the API call. To do so, you’ll need a state variable to keep track of order IDs. Add the following state variable declaration:
const [orderId, setOrderId] = useState("");
- Then, within
createOrder()
, add the following line undersetStartedOrder(true);
to set theorderId
state variable to the order ID of a successfully created (and therefore current) order:
setOrderId(generatedOrderId);
- Finally update the
<ItemDisplay />
line in yourreturn()
to the following to pass theorderId
state variable in as a prop:
<ItemDisplay orderId={orderId}/>
- Perfect! To keep track of our selected items, let’s do something similar in
/client/src/ItemDisplay.js
. In there, import theuseState
hook at the top just like we did withPOSHome
and initialize theitemsInOrder
andorderTotal
state variables like this:
const [itemsInOrder, setItemsInOrder] = useState([]);
const [orderTotal, setOrderTotal] = useState(0);
- Once the state variables have been added in, let’s add in a function called
addItemToOrder()
that will call the/orders/add-item
endpoint we made earlier. Add the following function to theItemDisplay
component above thereturn()
:
async function addItemToOrder (name, price) { // add in item to order var data = { orderId: props.orderId, itemName: name, itemPrice: roundedPrice }; var order = await fetch("http://localhost:9000/orders/add-item", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }); // if order was successful if (order.status === 200) { var roundedPrice = price.toFixed(2); // push item name to setItemsInOrder // add total to orderTotal setItemsInOrder([...itemsInOrder, { name: name, price: roundedPrice }]); setOrderTotal(orderTotal + price); }}
The function will take in an item’s name
and price
. Then, the data
object is declared that takes in orderId
, itemName
, and itemPrice
, the requirements for the request’s body. Finally, the request is made with all the
necessary data passed in. If the order ends up being successful, in order to display a price that has two decimal
places, the price
is converted using price.toFixed(2)
. Then, the item’s name
and price
are added to
the itemsInOrder
array, while the item’s price
is added to the order’s total.
- Add an
onClick
event to the<button>
tag in thereturn()
. Within the event, call theaddItemToOrder()
function. The tag should look like this:
<button
key={index}
onClick={() => {
addItemToOrder(item.name, item.price);
}}
>
This will fire the addItemToOrder()
function each time an item’s button is clicked.
- Within the main
<div>
in thereturn()
function, after the first nested<div>
, add a section to show an item’s name and price, as well as the order total. It will dynamically update as theorderTotal
anditemsInOrder
state variables are updated.
<div>
<h2>Items Ordered</h2>
<ul className="receipt">
{itemsInOrder.map((item, index) => {
return (
<li key={index}>
<div className="receiptEntry">
<div className="itemName">{item.name}</div>
<div className="itemPrice">{"$" + item.price}</div>
</div>
</li>
);
})}
</ul>
<p>
<b>Order Total:</b> ${(Math.round(orderTotal * 100) / 100).toFixed(2)}
</p>
</div>
- Finally, it’s time to test the functionality! Start up the frontend and backend of your application. Once the application loads, click the Create Order button. You should see the following page:
As you click on the buttons, the item name and price should appear under “Items Ordered", and the order total should also increase. Here’s an example of what it should look like if you click “Tea", “Coffee", and “Bagel":
To confirm items have been added to an order, go to your Serialized Dashboard > Data explorer > Aggregates > order (under Aggregate type column) > Aggregates > click the Aggregate ID of the top (and most recent) entry. You should then see a view like this:
If you click into any of the ItemAdded
Event IDs, you’ll see an object containing the data sent from the ItemAdded
event in your app:
The above ItemAdded
event was for a $2.50 bagel that was added to the order.
Completing Orders
The final use case will be completing orders. Once an order is completed from the ItemDisplay
component, the component
will disappear and the Create Order button will appear again to start a new order.
Let’s start in the backend!
- First, in
/client/api/order.js
, add in anOrderCompleted
event class:
class OrderCompleted { constructor(orderId, total) { this.orderId = orderId; this.total = total; } }
This event class requires an orderId
and a final order total
to complete the order.
- Similar to the
addOrder
flow, we’ll need to add a newcompleteOrder()
function to theOrder
class:
completeOrder(total) { if (!this.completed) { return [DomainEvent.create(new OrderCompleted(this.orderId, total))]; }else { // Don't emit event if already completed return []; } }
The above function will first check if an order is completed or not. If it isn’t completed, then a new event will be
created of the OrderCompleted
class type that was added above. It also passes in the necessary properties, taking
the orderId
from the Order object instance and passing in the total
.
- Next, add an
OrderCompleted
event handler:
OrderCompleted(state, event) { console.log("Handling OrderCompleted", event); return new Order(state).completeOrder({orderId: event.orderId, total: event.total, }); }
- Then, in
OrderState
, add acompleteOrder
function:
completeOrder(total) { return Object.assign({}, this, { completed: true, total: total }); }
- Next, in
api/order-client.js
, add in a function,completeOrder()
, to callcompleteOrder()
fromorder.js
:
async completeOrder(orderId, total) { await handleError(async () =>await this.client.update(orderId, (order) => { return order.completeOrder(total); })); }
- Finally, add in a
/orders/complete
route toapi/routes/orders.js
:
router.post("/complete", async function (req, res, next) { const { orderId, total } = req.body; console.dir(req.body);try { var response = await orderClient.completeOrder(orderId, total); res.send(response); } catch (error) { console.log(error); res.status(400).json({ error: error }); } });
Let’s jump back to the frontend for a bit.
- In order for this logic to work from
ItemDisplay
, you’ll need to update thestartedOrder
state variable from theItemDisplay
component. To do this, thesetStartedOrder
function can be passed in as a property fromPOSHome
. Inclient/src/components/POSHome.js
, pass insetStartedOrder
to the<ItemDisplay>
component so that it looks like this:
<ItemDisplay orderId={orderId} setStartedOrder={setStartedOrder}/>
- Now, in
/client/src/components/ItemDisplay.js
, add a new function,completeOrder()
. This will make a call to the/orders/complete
endpoint and pass in anorderId
variable from props as well as theorderTotal
state variable.
async function completeOrder() { // add in item to order var data = { orderId: props.orderId, total: orderTotal }; var
order = await fetch("http://localhost:9000/orders/complete", {
method: "POST", headers: {
"Content-Type": "
application / json" }, body: JSON.stringify(data), });
// if order was successful
if(order.status === 200)
{
props.setStartedOrder(false);
}
}
function exitOrder() {
props.setStartedOrder(false);
}
These two functions are the choices that a user can take when they’re on this screen. They can complete the order — in
which case the setStartedOrder()
function will be called and the state variable will be set to false
, triggering
that conditional statement we made earlier — or they can just exit everything. Link these up to buttons in our render
function so the user can call this code. It’s all coming together!
- Now it’s time to test your application! Run the frontend and backend in two different Terminal windows and test the
end-to-end flow. It should look like this:
- To confirm orders were marked as completed, go to your Serialized Dashboard and navigate
to Data explorer → Aggregates → order (under Aggregate type column) → Aggregates. Click the Aggregate ID of the top (
and most recent) entry. You should then see a view like this:
If you click on the Event ID for theOrderCompleted
event, it will surface data sent from the app (the order’s total amount):
Looking back
At this point, the only thing missing is a little CSS. This tutorial is already a bit long, so I’ll leave that as an exercise for the reader, but if you’d like, you can always check out what I wrote in the GitHub repo. This is what it ended up looking like:
I’m really satisfied with what we’ve created! We managed to use Serialized’s Aggregates API to create a very simple POS (point-of-sale) application so users can create orders, add items to an order, and either complete or exit the order. All events that occur within this order are sent to Serialized, where they are stored in groups of events, or Aggregates, with each Aggregate instance representing an order.
We might come back to this in the future to show off the other half of Serialized’s functionality that we haven’t even gotten to touch, but if you’re looking to build more on top of this application yourself, perhaps try to:
- Experiment with making the UI more sophisticated - adding pictures for items, adding more items, even adding item descriptions and sending these to Serialized!
- Add frontend and backend testing for the components, functionality, requests, and routes.
Thanks so much for following along! You can connect with me on Twitter and feel free to reach out if there are any questions or feedback. ⭐️