ESP32 Web Server using SPI Flash File System (SPIFFS)

In this article, we'll teach you how to build an ESP32 web server that serves HTML and CSS files saved on the filesystem. We'll generate separate HTML and CSS files rather than having to write the HTML and CSS text inside the Arduino code.

The web server we'll build will control an ESP32 output for demonstration reasons, but it's easily adaptable for other uses like displaying sensor readings.

Recommended reading: ESP8266 Web Server using SPIFFS

ESP32 Filesystem Uploader Plugin

You must have the ESP32 Filesystem Uploader plugin installed in your Arduino IDE to follow this tutorial. To install it first, if you haven't already, follow the instructions in the next tutorial:

Project Overview

It's important to outline what our web server will accomplish so that it's easier to comprehend before going straight to the project.

esp32 async web server
  • The web server you’ll build controls an LED connected to ESP32 GPIO 2. This is the ESP32 onboard LED. You can control any other GPIO.
  • The web server page shows two buttons: ON and OFF, to turn GPIO 2 on and off.
  • The web server page also shows the current GPIO state.

To demonstrate how everything works, the following figure shows a simplified diagram.

async web server spiffs
  • The ESP32 runs web server code based on the ESPAsyncWebServer library.
  • The HTML and CSS files are stored on the ESP32 SPIFFS (Serial Peripheral Interface Flash File System).
  • When you request a specific URL using your browser, the ESP32 responds with the requested files.
  • When you click the ON button, you are redirected to the root URL followed by /on, and the LED is turned on.
  • When you click the OFF button, you are redirected to the root URL followed by /off, and the LED is turned off.
  • On the web page, there is a placeholder for the GPIO state. The placeholder for the GPIO state is written directly in the HTML file between % signs, for example, %STATE%.

Installing Libraries

In the majority of our projects, the HTML and CSS files for the web server have typically been produced as strings right on the Arduino sketch You may write the HTML and CSS separately using SPIFFS and store them on the ESP32 filesystem.

The ESPAsyncWebServer library is one of the simplest ways to create a web server using files from the filesystem. On its GitHub website, the ESPAsyncWebServer library is well described. Visit the following website for further details about that library:

Installing the ESPAsyncWebServer library

Install the ESPAsyncWebServer library by following the next steps:

  • Click here to download the ESPAsyncWebServer library. You should have a .zip folder in your Downloads folder.
  • Unzip the .zip folder, and you should get the ESPAsyncWebServer-master folder.
  • Rename your folder from ESPAsyncWebServer-master to ESPAsyncWebServer.
  • Move the ESPAsyncWebServer folder to your Arduino IDE installation library folder.

Installing the Async TCP Library for ESP32

The ESPAsyncWebServer library requires the AsyncTCP library to work. To install that library, follow the next steps:

  • Click here to download the AsyncTCP library. You should have a .zip folder in your Downloads folder.
  • Unzip the .zip folder, and you should get the AsyncTCP-master folder.
  • Rename your folder from AsyncTCP-master to AsyncTCP.
  • Move the AsyncTCP folder to your Arduino IDE installation libraries folder.
  • Finally, re-open your Arduino IDE.

Organizing your Files

Three different files are required to construct the web server. The HTML file, CSS file, and Arduino sketch As shown below, the HTML and CSS files have to be saved inside the Arduino sketch folder in the data folder:

folder organization async spiffs

Creating the HTML File

This project's HTML is quite simple. Just a heading, a paragraph displaying the GPIO status, and two buttons are needed for the web page.

Alternatively, you can download all the project files here and create an index.html file with the following content:

<!DOCTYPE html>
<html>
<head>
  <title>ESP32 Web Server</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" href="data:,">
  <link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
  <h1>ESP32 Web Server</h1>
  <p>GPIO state: <strong> %STATE%</strong></p>
  <p><a href="/on"><button class="button">ON</button></a></p>
  <p><a href="/off"><button class="button button2">OFF</button></a></p>
</body>
</html>

We need to reference the CSS file in the HTML text since we're using CSS and HTML in distinct files. It is necessary to insert the following line between the head> and /head> tags:

<link rel="stylesheet" type="text/css" href="style.css">

The link> tag tells the HTML file that you are using an external style sheet to format the appearance of the page. In this situation, the external file is a stylesheet—the CSS file—which will be used to change the look of the page; the rel attribute specifies the kind of external file.

You can see that you're using a CSS file for the styling by looking at the type attribute, which is set to “text/css“. Since the CSS and HTML files will be in the same folder, you just need to reference the file name: style.css. The href attribute identifies the file location.

We write the first heading of our web page on the line that follows. An “ESP32 Web Server” is what we have here. The heading may be changed to whatever text you like.

<h1>ESP32 Web Server</h1>

Following the GPIO state, we add a paragraph with the text “GPIO state:” We may add a placeholder that will be replaced with any value we set on the Arduino sketch since the GPIO state varies depending on the state of the GPIO.

We use % signs to add placeholders. We may, for instance, use %STATE% to create a placeholder for the state.

<p>GPIO state: <strong>%STATE%</strong></p>

The Arduino sketch assigns a value to the placeholder for STATE.

We then create an ON button and an OFF button. When you click on a button, we follow the web page to root, followed by /on URL. You are redirected to the /off URL when you click the off button.

<p><a href="/on"><button class="button">ON</button></a></p>
<p><a href="/off"><button class="button button2">OFF</button></a></p>

Creating the CSS file

Create the following CSS style.css file, or download the whole project files here:

html {
  font-family: Helvetica;
  display: inline-block;
  margin: 0px auto;
  text-align: center;
}
h1{
  color: #0F3376;
  padding: 2vh;
}
p{
  font-size: 1.5rem;
}
.button {
  display: inline-block;
  background-color: #008CBA;
  border: none;
  border-radius: 4px;
  color: white;
  padding: 16px 40px;
  text-decoration: none;
  font-size: 30px;
  margin: 2px;
  cursor: pointer;
}
.button2 {
  background-color: #f44336;
}

This is just a simple CSS file to orient the page and set the text size, style, and color of the buttons. CSS won't be described in detail here. The W3Schools website is a good place to learn about CSS.

Arduino Sketch

Download all the project files here, or copy the following code to the Arduino IDE. Then, to make it work, you need to type your network credentials (SSID and password).

/*********
  LEDEdit PRO
  Complete project details at https://lededitpro.com  
*********/

// Import required libraries
#include "WiFi.h"
#include "ESPAsyncWebServer.h"
#include "SPIFFS.h"

// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

// Set LED GPIO
const int ledPin = 2;
// Stores LED state
String ledState;

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);

// Replaces placeholder with LED state value
String processor(const String& var){
  Serial.println(var);
  if(var == "STATE"){
    if(digitalRead(ledPin)){
      ledState = "ON";
    }
    else{
      ledState = "OFF";
    }
    Serial.print(ledState);
    return ledState;
  }
  return String();
}
 
void setup(){
  // Serial port for debugging purposes
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);

  // Initialize SPIFFS
  if(!SPIFFS.begin(true)){
    Serial.println("An Error has occurred while mounting SPIFFS");
    return;
  }

  // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi..");
  }

  // Print ESP32 Local IP Address
  Serial.println(WiFi.localIP());

  // Route for root / web page
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(SPIFFS, "/index.html", String(), false, processor);
  });
  
  // Route to load style.css file
  server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(SPIFFS, "/style.css", "text/css");
  });

  // Route to set GPIO to HIGH
  server.on("/on", HTTP_GET, [](AsyncWebServerRequest *request){
    digitalWrite(ledPin, HIGH);    
    request->send(SPIFFS, "/index.html", String(), false, processor);
  });
  
  // Route to set GPIO to LOW
  server.on("/off", HTTP_GET, [](AsyncWebServerRequest *request){
    digitalWrite(ledPin, LOW);    
    request->send(SPIFFS, "/index.html", String(), false, processor);
  });

  // Start server
  server.begin();
}
 
void loop(){
  
}

How the Code Works

First, include the necessary libraries:

#include "WiFi.h" 
#include "ESPAsyncWebServer.h" 
#include "SPIFFS.h"

The following variables need your network credentials typed in:

const char* ssid = "REPLACE_WITH_YOUR_SSID"; 
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

Next, create a variable named ledPin that refers to GPIO 2 and a String variable named ledState that holds the led state.

const int ledPin = 2;
String ledState;

Create an AsynWebServer object called server that is listening on port 80.

AsyncWebServer server(80);

processor()

The processor() function will create an attribute for the placeholder value we've added to the HTML file. It takes the placeholder as input and should return a String that replaces the placeholder. The following structure should be used in the processor() function:

String processor(const String& var){
  Serial.println(var);
  if(var == "STATE"){
    if(digitalRead(ledPin)){
      ledState = "ON";
    }
    else{
      ledState = "OFF";
    }
    Serial.print(ledState);
    return ledState;
  }
  return String();
}

The STATE we created in the HTML file is the first thing this function checks to see whether the placeholder is that.

if(var == "STATE"){

If so, we set the ledstate variable to either ON or OFF according to the LED state.

if(digitalRead(ledPin)){
  ledState = "ON";
}
else{
  ledState = "OFF";
}

Finally, we return the ledState variable. This replaces the placeholder with the ledState string value.

return ledState;

setup()

In the setup(), start by initializing the Serial Monitor and setting the GPIO as an output.

Serial.begin(115200);
pinMode(ledPin, OUTPUT);

Initialize SPIFFS:

if(!SPIFFS.begin(true)){
  Serial.println("An Error has occurred while mounting SPIFFS");
  return;
}

Wi-Fi connection

Connect to Wi-Fi and print the ESP32 IP address:

WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.println("Connecting to WiFi..");
}
Serial.println(WiFi.localIP());

Async Web Server

We can set the routes where the server will be listening for incoming HTTP requests and execute functions when a request is received on those routes using the ESPAsyncWebServer library. Use the server object's on() method as follows to do that:

server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send(SPIFFS, "/index.html", String(), false, processor);
});

The server will send the index.html file to the client whenever it receives a request on the root “/” URL. Since the processor is the send() function's final input, we may replace the placeholder with whatever value we like—in this example, ledState.

The client will request the CSS file since we have referenced it in the HTML file. When this happens, the CSS file is sent to the client.

server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send(SPIFFS, "/style.css","text/css");
});

Finally, you'll need to specify what happens on the /on and /off routes. When a request is made through certain routes, the LED is turned on or off, and the ESP32 serves the HTML file.

server.on("/on", HTTP_GET, [](AsyncWebServerRequest *request){
  digitalWrite(ledPin, HIGH);
  request->send(SPIFFS, "/index.html", String(),false, processor);
});
server.on("/off", HTTP_GET, [](AsyncWebServerRequest *request){
  digitalWrite(ledPin, LOW);
  request->send(SPIFFS, "/index.html", String(),false, processor);
});

In the end, we use the begin() method on the server object, so that the server starts listening for incoming clients.

server.begin();

Because this is an asynchronous web server, all requests may be defined in setup(). Then, while the server is listening for incoming clients, you may add other code to the loop().

Uploading Code and Files

You can either download all the project files here or save the code as Async_ESP32_Web_Server. Create a folder named “data” by selecting Sketch > Show Sketch Folder. The HTML and CSS files should be saved in that folder.

The code should then be uploaded to your ESP32 board. Be sure you choose the right board and COM port. Also, make sure your network credentials are included in the code.

Arduino 2.0 Upload Button

You need to upload the files after uploading the code. Wait for the upload of the files by going to Tools > ESP32 Data Sketch Upload.

ESP32 Data Sketch Upload

Open the Serial Monitor at a baud rate of 115200 after everything has been successfully uploaded. When you press the ESP32 “ENABLE” button, the ESP32 IP address should print.

esp32 ip address

Demonstration

Type the ESP32 IP address into your browser once it's open. To control the on-board LED of the ESP32, press the ON and OFF buttons. Additionally, make sure the GPIO state is being updated properly.

esp32 web server spiffs example

Conclusion

Instead of needing to write all the code inside the Arduino sketch, the SPI Flash File System (SPIFFS) is particularly useful to store HTML and CSS files to serve to a client.

By running a specific function in response to a specific request, the ESPAsyncWebServer library enables you to create a web server. Additionally, placeholders that may be replaced with variables, such as sensor readings or GPIO states, can be included in the HTML file.

If you like ESP32, you may also like:

We hope you find this tutorial useful. Thanks for reading.

Oh hi there It’s nice to meet you.

Sign up to receive awesome content in your inbox, every month.

We don’t spam! Read our privacy policy for more info.

Leave a Reply

Your email address will not be published. Required fields are marked *

Developing IoT Projects with ESP32

Automate your home or business with inexpensive Wi-Fi devices