Your First Embedded Single Page Application

A Single Page Application (SPA) is a cutting-edge web application model. Instead of reloading the entire page every time you interact with it, a SPA loads all its content just once. After this initial load, the application operates within the browser, updating only the parts that change based on your interactions. This makes SPAs highly responsive, even on basic microcontrollers.

In this tutorial, we'll explore the Minnow Server Reference Example in a simplified manner, using the Barracuda App Server as our backend instead of the Minnow Server. You can run the included ready-to-use example either with our Mako Server or on our ESP32 microcontroller tool, known as Xedge32. The Mako Server and Xedge32 are ready-to-run application servers based on the Barracuda App Server source code library.

This tutorial can be used for several purposes:

  1. Introduction to WebSocket JSON Messaging: Understand the WebSocket JSON messages exchanged between the Minnow Server Reference Example's SPA and the server. The server-side code we provide for this tutorial prints all JSON messages sent between the server and the SPA, making it easier to grasp the Minnow Server Reference Example's C server-side code.
  2. An Alternative to the Minnow Server: By comparing the C code in the Minnow Server Reference Example with the server-side Lua code in this tutorial, you'll see how the Barracuda App Server and Lua can significantly reduce the code you need to write; the Lua code is substantially smaller. Plus, Lua code is more straightforward and more intuitive than C code.
  3. For Xedge32 Developers: Get introduced to the concept of responsive SPA applications as opposed to traditional server-side rendering. While the ESP32 performs decently and the tutorial Your First Embedded Server Side Generated Application works well, you'll find that the SPA approach is snappier, especially over secure TLS connections.

Note: This tutorial does not delve into the inner workings of the Minnow Server Reference Example's SPA (the front end). For a deep dive into that, refer to the Minnow Server Reference Example's SPA intro. We'll treat the application as a "black box" here. For example, suppose you're an embedded developer aiming to design a professional UI. In that case, you might prefer to delegate the SPA development to experts, treating it as a "black box", and focus solely on the server-side communication API defined for the SPA.


Before diving into the tutorial, you must set up your environment. Start by downloading the necessary files from GitHub:

  1. Open your terminal or command prompt.
  2. Enter the following commands to clone the required repositories:
       git clone
       git clone

Using the Mako Server

The Mako Server is a great tool to help you understand the WebSocket communication between the SPA and its server counterpart.

Note: When using the Mako Server for this tutorial, remember it's in simulation mode. This means it's excellent for learning but won't connect to actual hardware.

Here's how to set it up:

  1. After you have cloned the GitHub repositories mentioned above, launch the Mako Server with the following command:
    mako -lminnow::MinnowServer/www -l::LSP-Examples/MinnowSim

    The above command instructs the Mako Server to load two applications: the SPA and the server-side code. The SPA's base URL is set to http://localhost/minnow/, and the server-side code's base URL is set to http://localhost/. You must use these base addresses to get the complete application working.

  2. Once the server is up and running, open your web browser and navigate to http://localhost.
  3. You'll be prompted to log in. Use the username root and the password password.

For a more visual guide on starting the Mako Server, we have a video tutorial on command line instructions. Additionally, you can explore all the command line options for the server in our official documentation.

Using Xedge32

Xedge32 is the ultimate tool to use for our example program's server-side Lua code. With Xedge32, you can control GPIOs (General-Purpose Input/Output) directly from the SPA's web user interface. See the Xedge32 introduction for selecting an ESP32 and flashing the Xedge32 firmware.

What Can You Do?

  • The SPA includes a dedicated LED page, allowing you to control LEDs via the user interface.
  • If you add a push button for each LED, the web UI will also automatically update based on the button's state.
  • When you press a push button, an interrupt is generated, and a WebSocket message is sent from the ESP32 to all connected browsers, updating the LED's on/off status.

Our Setup: In our test setup, as shown below, we have four LEDs and four corresponding push buttons. The ESP32’s left-side GPIOs are used for controlling the LEDs and the ESP32’s right side GPIOs are used for reading the push buttons’ state asynchronously – i.e. in interrupt mode.

ESP32 LED and button test bench

We have chosen the compact and breadboard-friendly Seeed Studio XIAO ESP32S3 for our test bench. But don't worry; you can use any ESP32 compatible with Xedge32.

Wiring Guide:

  • LEDs: Connect the ESP32’s 3V to the LED's positive pin. Then, connect the LED's negative pin to the ESP32's GPIO PIN. Use GPIO1 to GPIO4 for the LEDs, dedicating one GPIO for each LED.
  • Buttons: Connect the 3V to one pin of the button. Then, connect the button's other pin to the ESP32's GPIO PIN. Use GPIO9, GPIO8, GPIO7, and GPIO44 for the buttons, with one GPIO assigned to each button.

New to Wiring LEDs? Check out our beginner-friendly tutorial: Your First Xedge32 Project If this is your first time setting up LEDs.

The following figure shows the GPIOs for the XIAO ESP32S3.


Testing Your ESP32 Wiring

Once you have set up the ESP32 according to the instructions provided, it's a good idea to test the wiring to ensure everything is connected correctly before diving into the SPA.

To help you with this, we have provided a simple Lua script. This script will allow you to verify your wiring quickly. The Lua code is designed to configure and test the LEDs and buttons on the ESP32. The code below uses the Xedge32’s GPIO API.

local function cfgButton(pin,cb)
   local cfg={
   return esp32.gpio(pin,"IN", cfg)

local ledData -- initialized below

local function buttonClick(index)
   local ledObj = ledData[index]
   local state = not ledObj.state -- toggle
   ledObj.state = state -- update
   ledObj.led:value(state) -- set LED

local function cfgLed(pin)
   local led=esp32.gpio(pin,"OUT")
   led:value(true) -- Set high: led off
   return led

      button=cfgButton(9, function() buttonClick(1) end),
      button=cfgButton(8, function() buttonClick(2) end),
      button=cfgButton(7, function() buttonClick(3) end),
      button=cfgButton(44,function() buttonClick(4) end),

Function cfgButton:

  • This function sets up a button on a specified GPIO pin.
  • The pulldown=true ensures that the pin is in a known state (low) when the button is not pressed.
  • The type="POSEDGE" configures the button to detect a rising edge, which means it will trigger when the button is pressed.
  • The callback=cb assigns a function to be called when the button is pressed.
  • The esp32.gpio(pin,"IN", cfg) initializes the specified GPIO pin as an input with the given configuration.

Function buttonClick:

  • This function is called (triggered by interrupt) when a button is pressed.
  • It toggles the state of the corresponding LED (turns it on if it was off, and vice versa).
  • The trace function logs which button was clicked.

Function cfgLed:

  • This function sets up an LED on a specified GPIO pin.
  • The LED is initialized as an output.
  • The initial state of the LED is set to off (led:value(true)).

Lua table ledData:

  • This table contains the configuration and state of each LED and button pair.
  • Each entry in the table represents one LED-button pair, with the button's GPIO pin, the LED's GPIO pin, and the initial state of the LED.

How to run the LED and button test code on the ESP32

Using the Xedge32 IDE, create an application and then create an Xlua file – e.g testled.xlua. Copy the above code and paste the code into the Xlua file using the Xedge32 IDE. See the tutorial Your First Xedge32 Project if you are new to Xedge32. Save the file to start the Xlua file. You should now be able to turn the LEDs on and off using the buttons.

Note: Button bounce occurs when a button oscillates between on and off states briefly after being pressed, causing unintended multiple interrupts. This rapid, noisy transition can lead to unpredictable button click behavior. The above Lua code does not include debounce logic to counteract this effect. Consider adding debounce measures for consistent button responses if using this code in practical applications.

Uploading the SPA and Server-Side Code

Once you have verified the LED and button test program works, follow these steps to upload the SPA and server-side code:


Ensure the LED/button test program is turned off to free up the GPIO resources for the SPA's server-side code.

Packaging the SPA:Since we are treating the SPA web app as a black box, we will package the app as a deployed application. A deployed application is simply a ZIP file containing all files that are part of the application.

  1. Zip the SPA application found in MinnowServer/www . Exclude the "www" folder name from the ZIP and name the file
  2. Access the web file manager via http://esp32-ip-addr/rtl/apps/disk/.
  3. Upload by dragging and dropping it onto the browser window.
  4. Create a new folder named "server" using the + symbol.
  5. Click on the "server" folder to navigate to http://esp32-ip-addr/rtl/apps/disk/server/.
  6. Upload the files from LSP-Examples/MinnowSim/ folder by dragging and dropping them onto the browser window.
  7. Creating the SPA and server app in the IDE:
    1. Go to the IDE at http://esp32-ip-addr/rtl/.
    2. In the left pane, right-click > "New App" > click "Running" > name it "spa" > click "LSP App" > set the base URL as "minnow" > save.
    3. In the left pane, right-click "server" > "New App" > click "Running" > name it "server" > click "LSP App" > ensure the base URL is empty > save.
  8. Testing the SPA:
    1. Visit http://esp32-ip-addr/, which redirects to http://esp32-ip-addr/minnow/.
    2. Log in using "root" for the username and "password" for the password.
    3. In the SPA, access the LEDs page. You can now control the LEDs.Toggling an LED button should also reflect its state in the SPA.


Congratulations on completing this tutorial! You've successfully set up and tested a Single Page Application (SPA) with the ESP32, delving deep into GPIO configurations and server-side integrations. By packaging the SPA and deploying the server-side code, you've navigated the complexities of embedded SPA web development, laying a strong foundation for future projects.

Building on this, it's worth noting the advantages of the Barracuda App Server over the simpler Minnow Server. Specifically, the Barracuda App Server's support for multiple WebSocket connections. The Lua server-side code in our example is tailored to leverage this feature. Try opening multiple browser windows and connecting them all to the server-side code; you'll observe that the LED page seamlessly synchronizes the LED on/off state across all connected browsers. This synchronization is achieved using the design pattern detailed in the tutorial Modern Approach to Embedding a Web Server in a Device.

Dive Deeper:

Now that you have taken your first steps into designing a professional embedded SPA, there's so much more to explore. Continue your journey and expand your expertise with our comprehensive Xedge32 tutorials.

Looking for Guidance?

Our extensive tutorials on embedded web servers and IoT are your roadmap to success. But every journey can have its challenges. Our seasoned experts are here to pave the way if you're pressed for time or hit a roadblock. At Real Time Logic, we equip you with knowledge and offer a helping hand when you need it most. Together, let's achieve the extraordinary!


OPC-UA Client & Server

An easy to use OPC UA stack that enables bridging of OPC-UA enabled industrial products with cloud services, IT, and HTML5 user interfaces.

Edge Controller

Edge Controller

Use our user programmable Edge-Controller as a tool to accelerate development of the next generation industrial edge products and to facilitate rapid IoT and IIoT development.

On-Premises IoT

On-Premises IoT Platform

Learn how to use the Barracuda App Server as your On-Premises IoT Foundation.

Embedded Web Server

Barracuda Embedded Web Server

The compact Web Server C library is included in the Barracuda App Server protocol suite but can also be used standalone.

WebSocket Server

Microcontroller Friendly

The tiny Minnow Server enables modern web server user interfaces to be used as the graphical front end for tiny microcontrollers. Make sure to check out the reference design and the Minnow Server design guide.

WebDAV Server

Network File System

Why use FTP when you can use your device as a secure network drive.

HTTP Client

Secure HTTP Client Library

PikeHTTP is a compact and secure HTTP client C library that greatly simplifies the design of HTTP/REST style apps in C or C++.

WebSocket Client

Microcontroller Friendly

The embedded WebSocket C library lets developers design tiny and secure IoT applications based on the WebSocket protocol.

SMTP Client

Secure Embedded SMTP Library

Send alarms and other notifications from any microcontroller powered product.

Crypto Library

RayCrypto C Library

The RayCrypto engine is an extremely small and fast embedded crypto library designed specifically for embedded resource-constrained devices.

Embedded PKI Service

Automatic SSL Certificate Management for Devices

Real Time Logic's SharkTrust™ service is an automatic Public Key Infrastructure (PKI) solution for products containing an Embedded Web Server.


Modbus TCP client

The Modbus client enables bridging of Modbus enabled industrial products with modern IoT devices and HTML5 powered HMIs.

Posted in Xedge32