Build a Point-of Sale App with Serialized
Tags: eventdriven, webdev, tutorial, javascript
Published: 2022-03-21 22:33:32

_This tutorial was originally written for the Serialized platform. You can view the original post on Serialized's blog [here](https://serialized.io/blog/build-a-point-of-sale-app-with-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:

![User flow diagram](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p124uocl4kyykezpczh1.png)

The three main functionalities we’ll focus on in this tutorial are:

1. creating new orders,
2. adding items to existing orders, and
3. marking orders as completed. 

These three scenarios will capture the use cases our very basic POS system. The final product will look like this:

![Final product GIF of POS system](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8uyw9ijospo9lxs8cm6i.gif)

## 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](https://nodejs.org/en/download/).
- **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 run `npx -v`. If no version pops up, you can install `npx` using the instructions [here](https://www.npmjs.com/package/npx).
- **Serialized:** To use the Serialized API, you’ll need to [create an account](https://app.serialized.io/auth/signup). 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](https://docs.serialized.io/api-reference/basics/projects).

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](https://github.com/hello-ashleyintech/pos-app-for-orders). All instructions for running the project are available in the repository’s [README.md](https://github.com/hello-ashleyintech/pos-app-for-orders/blob/master/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](https://www.freecodecamp.org/news/create-a-react-frontend-a-node-express-backend-and-connect-them-together-c5798926047c/).

1. To start, initialize the project directory on your machine in your location of choice by running `mkdir pos-app` or creating a `pos-app` folder manually. `cd` into it in you Terminal and run
    
    ```bash
     npx create-react-app client
    ```
    
    This will create a folder named `client` where your application’s frontend will live.
    
2. Once the `client` folder has been created, run the following commands to enter the newly created `client` folder, and then start the frontend server:
    
    ```jsx
    cd client
    npm start
    ```
    
    If your project has been set up correctly, you should see the default React app in your browser at `[localhost:3000](http://localhost:3000)`:
    
    ![React app starter page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fq7shcxuz4e1s20i7ixx.png)
    
3. 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:
    
    ```bash
    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`:

    ![Express starter page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/soa6vpfgds6sq341kdfz.png)
    
    You can learn more about the `express-generator` package used to set up the backend [here](https://expressjs.com/en/starter/generator.html).
    
4. 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 the `bin/www` file in the `api` directory. Update line 15 so its default value now points to port 9000. The line will look like this once updated:
    
    ```jsx
    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

1. 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:
    
    ```bash
    npm install @serialized/serialized-client
    ```
    
2. Once the client has been installed, create a `.env` file in the `api` 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:
    
    ```bash
    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](https://serialized.io/docs/aggregates/) to create a new order in our POS system. 

1. To get started, create an `order.js` file within the `api` 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 the `order.js` file:
    
    ```jsx
    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 given `orderId` exists and matches the 36-character UUID format specified for order IDs. Then it’ll initialize our new order-creation event with a call to `DomainEvent.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 the `OrderState` object to keep track of the Order’s state.

- **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 an `orderId`, 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`’s `completed` property being set to `true`.
    
2. Once your `order.js` file is set up, add in an `order-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 in `order.js`. Paste in the following code to the `order-client.js` file:
    
    ```jsx
    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.
    
3. Once `order.js` and `order-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 the `api/routes` directory and create a file called `orders.js` with the following code inside:
    
    ```jsx
    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`.
    
4. Now that the `orders.js` route has been created, it needs to be added to the `app.js` in the `api` directory so it can be called within the app. Add an initialization for an `ordersRouter` variable on line 9 in `api/app.js`:
    
    ```jsx
    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:
    
    ```jsx
    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. 

1. Browsers often enforce a same-origin policy for requests, resulting in CORS ([Cross-Origin Resource Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)) errors in the event that requests on a certain domain are made from a different origin domain. This example uses `[localhost:3000](http://localhost:3000)` for the frontend while retrieving information from a `[localhost:9000](http://localhost: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](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy). To prevent CORS errors in your app once the frontend and backend are wired up, install the CORS package in `api` with the following command:
    
    ```bash
    npm install --save cors
    ```
    
2. In `api/app.js`, add the following on line 6 to add in the CORS package that was just installed to the backend: 
    
    ```jsx
    var cors = require("cors");
    ```
    
    Then at line 23, add the following line to instruct your Express app to use the CORS package: 
    
    ```bash
    app.use(cors());
    ```
    
    It might be worth checking `api/app.js` against [the GitHub repo](https://github.com/hello-ashleyintech/pos-app-for-orders) at this point, just to make sure everything is set up right.
    
3. In the `client` directory, create a new folder inside `src` called `components` and initialize a file called `POSHome.js`:
    
    ```jsx
    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.
    
4. Replace `client/src/App.js` with the following code so that the `POSHome` component is being passed in to the main application and is visible from the main page:
    
    ```jsx
    import "./App.css";
    import POSHome from "./components/POSHome";
    
    function App() {
      return (
        <div className="App">
            <POSHome />
        </div>
      );
    }
    
    export default App;
    ```
    
5. 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, run `npm start` in the `client` folder. Once `[localhost:3000](http://localhost:3000)` launches the frontend, you’ll see the following screen:   

    ![POS System screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gppdvck9c21z15cvyg75.png)
    
    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:
    
    ![Screenshot of Serialized dashboard with new order entry](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kvzeowacn44y8knc3zr8.png)
    
    If you check the Terminal tab or window where you’re running the `api` server, you’ll also see something like the following:
    
    ```bash
    OPTIONS /orders/create 204 0.236 ms - 0
    { orderId: 'd3ce8600-9e71-4417-9726-ab3b9056df48' }
    POST /orders/create 200 719.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.

1. 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 called `data` and add in an `items.json` file. Within the file, set up something like this:
    
    ```bash
    {
        "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.
    
2. 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 an `ItemDisplay.js` file for a new checkout flow component. Here’s what that might look like:
    
    ```jsx
    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.
    
3. Now, let’s update `client/src/components/POSHome.js` so that when an order is created, it’ll display the `ItemDisplay` component. We’ll use state variables for that — it’s great for conditionally rendering components. To start, update the `import` line at the top of `POSHome.js` so it imports the `useState` hook too. While we’re there, bring in the `ItemDisplay` component from earlier.
    
    ```bash
    import React, { useState } from "react";
    import ItemDisplay from "./ItemDisplay";
    ```
    
4. The `useState` hook will initialize a state variable for us and give us a way to update it in the future. Let’s start with `startedOrder` — this will keep track of whether an order has been started, and if so, it will display the `ItemDisplay` component. The variable will be initialized on line 5 with an initial value of `false` using the following:
    
    ```jsx
    const [startedOrder, setStartedOrder] = useState(false);
    ```
    
5. Next, update your `return()` function in your `POSHome` component so that it looks like the following:
    
    ```jsx
    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.”
    
6. The final piece of this is setting `startedOrder` to `true` when an order is created. This can be done in the `createOrder()` function above. Add the following block inside the function on line 15:
    
    ```jsx
    // if order was successful
    if (order.status === 200) {
      setStartedOrder(true);
      setOrderId(generatedOrderId);
    }
    ```
    
7. Now it’s time to test the flow! Load up the frontend and backend of your application by running `npm start` in both the `api` and `client` directories in two different Terminal tabs or windows. Once the `client` has loaded, you should see your application appear in `localhost: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 the `ItemDisplay` 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.

   ![POS system order page with buttons for items](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/phh4zl7gz8cko9yb6nxa.png)
    

## 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.

1. In `/client/api/order.js`, add in an `ItemAdded` event class under where the `OrderCreated` class is declared:
    
    ```jsx
    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`.
    
2. Add an `itemAdded()` function to your `Order` class by adding the following code at line 19:
    
    ```jsx
    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. 
    
3. In the `Order` class, add a new event handler for an item to be added:
    
    ```jsx
    ItemAdded(state, event) {
      console.log("Handling ItemAdded", event);
      return new Order(state).addItem({
        orderId: event.orderId,
        itemName: event.itemName,
        itemPrice: event.itemPrice
      });
    },
    ```
    
4. Add the following inside the `OrderState` class at line 64:
    
    ```jsx
    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](https://github.com/hello-ashleyintech/pos-app-for-orders) to make sure it lines up.
    
5. Once `api/order.js` has been updated, jump into the `order-client.js` file to add an `addItem()` function that will query the `addItem()` logic that was just added. Paste the following inside the `OrderClient` class at line 24:
    
    ```jsx
    async addItem(orderId, itemName) {
      await handleError(
        async () =>
          await this.client.update(orderId, (order) => {
            return order.addItem(itemName);
          })
      );
    }
    ```
    
6. 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:
    
    ```jsx
    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.

1. To start, go to `/client/src/POSHome.js`. Since the `/add-item` request takes in an `orderId`, we need to pass it in to the `ItemDisplay` 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 variabl declaration:
    
    ```jsx
    const [orderId, setOrderId] = useState("");
    ```
    
2. Then, within `createOrder()`, add the following line under `setStartedOrder(true);` to set the `orderId` state variable to the order ID of a successfully created (and therefore current) order:
    
    ```jsx
    setOrderId(generatedOrderId);
    ```
    
3. Finally update the `<ItemDisplay />` line in your `return()` to the following to pass the `orderId` state variable in as a prop:
    
    ```jsx
    <ItemDisplay orderId={orderId} />
    ```
    
4. Perfect! To keep track of our selected items, let’s do something similar in  `/client/src/ItemDisplay.js`. In there, import the `useState` hook at the top just like we did with `POSHome` and initialize the `itemsInOrder` and `orderTotal` state variables like this:
    
    ```jsx
    const [itemsInOrder, setItemsInOrder] = useState([]);
    const [orderTotal, setOrderTotal] = useState(0);
    ```
    
5. 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 the `ItemDisplay` component above the `return()`:
    
    ```jsx
    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.
    
6. Add an `onClick` event to the `<button>` tag in the `return()`. Within the event, call the `addItemToOrder()` function. The tag should look like this:
    
    ```jsx
    <button
      key={index}
      onClick={() => {
        addItemToOrder(item.name, item.price);
      }}
    >
    ```
    
    This will fire the `addItemToOrder()` function each time an item’s button is clicked.
    
7. Within the main `<div>` in the `return()` 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 the `ordreTotal` and `itemsInOrder` state variables are updated.
    
    ```jsx
    <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>
    ```
    
8. 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:
    
    ![POS order page with items listed and items ordered section](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/00184lur726ygniu6414.png)
    
    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”:

    ![POS system with list of items selected](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q6y8i6cbl92yx8tl0st3.png)
    
    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:

    ![Aggregate view of Serialized](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v36tce920pu2cyu98mzd.png)
    
    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:

    ![Item added event IDs page in Serialized](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f6fn1r7ywvcww1j1xph4.png)
    
    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!

1. First, in `/client/api/order.js`, add in an `OrderCompleted` event class:
    
    ```jsx
    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.
    
2. Similar to the `addOrder` flow, we’ll need to add a new `completeOrder()` function to the `Order` class:
    
    ```jsx
    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`. 
    
3. Next, add an `OrderCompleted` event handler:
    
    ```jsx
    OrderCompleted(state, event) {
      console.log("Handling OrderCompleted", event);
      return new Order(state).completeOrder({
        orderId: event.orderId,
        total: event.total,
      });
    },
    ```
    
4. Then, in `OrderState`, add a `completeOrder` function:
    
    ```jsx
    completeOrder(total) {
      return Object.assign({}, this, { completed: true, total: total });
    }
    ```
    
5. Next, in `api/order-client.js`, add in a function, `completeOrder()`, to call `completeOrder()` from `order.js`:
    
    ```jsx
    async completeOrder(orderId, total) {
      await handleError(
        async () =>
          await this.client.update(orderId, (order) => {
            return order.completeOrder(total);
          })
      );
    }
    ```
    
6. Finally, add in a `/orders/complete` route to `api/routes/orders.js`:
    
    ```jsx
    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.

1. In order for this logic to work from `ItemDisplay`, you’ll need to update the `startedOrder` state variable from the `ItemDisplay` component. To do this, the `setStartedOrder` function can be passed in as a property from `POSHome`. In `client/src/components/POSHome.js`, pass in `setStartedOrder` to the `<ItemDisplay>` component so that it looks like this:
    
    ```jsx
    <ItemDisplay orderId={orderId} setStartedOrder={setStartedOrder} />
    ```
    
2. 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 an `orderId` variable from props as well as the `orderTotal` state variable.
    
    ```jsx
    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!
    
3. 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:

    ![GIF of full POS order flow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r4tfjn25unt6z9xnsv04.gif)
    
4. 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:

    ![Serialized aggregate event list](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tiqi9wsurrrk6g7ugrzx.png)
    
    If you click on the Event ID for the `OrderCompleted` event, it will surface data sent from the app (the order’s total amount):
    
    ![Serialized event data dump with order amount](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/48idv731nqyu86qdlzvi.png)
    

## 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](https://github.com/hello-ashleyintech/pos-app-for-orders). This is what it ended up looking like:

![Completed POS app with CSS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nqlnrzog31oy2lvwebzt.gif)

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](https://twitter.com/ashley_in_tech) and feel free to reach out if there are any questions or feedback. ⭐️