ESP32 Arduino: Serving Bootstrap

In this tutorial we will check how to serve the Boostrap framework files from the ESP32, using the Arduino core and the async HTTP web server library. We will also develop a very simple web page with a Boostrap component, which will also be served from the ESP32. The tests were performed using a DFRobot’s ESP32 module integrated in a ESP32 development board.

Introduction

In this tutorial we will check how to serve the Boostrap framework files from the ESP32, using the Arduino core and the async HTTP web server library. We will also develop a very simple web page with a Boostrap component, which will also be served from the ESP32.

Boostrap is a very popular frontend component library [1] that makes it very easy to develop web applications. You can check Bootstrap’s page here. The list of available components can be consulted here.

Since Bootstrap depends on jQuery for some functionalities [2], we will also need this library.

We will upload the source files and dependencies needed for the Boostrap framework to the SPIFFS file system of the ESP32, so later we can serve them to a client.

To make the uploading procedure easier, we will be using this Arduino IDE plugin, which allows to upload files to the SPIFFS file system of the ESP32. For a tutorial on how to get started using it, please check this previous tutorial.

Note that we have used the most recent versions of both Bootstrap (4.3.1) and jQuery  (3.3.1) available at the time of writing. Naturally, more recent versions may have already appeared when you are following this tutorial.

The tests were performed using a DFRobot’s ESP32 module integrated in a ESP32 development board.

Obtaining and uploading the files

First of all, we should create a folder named “data” somewhere in our computer. This will be the folder where we will place all the files that will be uploaded to the ESP32.

Inside the data folder we will create an empty file called “index.html” and a folder called src.

The “index.html” file will contain the HTML code of the web page that we will be serving from our web server. This web page will make use of a Boostrap component. The src folder will contain all the Boostrap framework files and dependencies. For now, we will leave them empty after their creation.

After creating both the src folder and the file, your data folder should look like figure 1.

Project folder for ESP32 Arduino serving bootstrap

Figure 1 – Organization of the data folder.

After this, we will get the Bootstrap files. The files can be downloaded from this page. After downloading and unzipping the content, you should get a folder named bootstrap-4.3.1-dist.

This folder should contain two folders with the following names:

  • css
  • js

If you open each of these two folders, you will check that they contain a lot of files. Nonetheless, for this tutorial, we will only need one file from each.

So, first navigate to the css folder. Inside there should be a file called “bootstrap.min.css“, as illustrated in figure 2.

Note that we should copy the file “bootstrap.min.css“, highlighted in red and not the file bootstrap.min.css.map“, which has a blue cross in front of it. This may be confusing because some file systems show the name of the file and the extension separated.

Getting Bootstrap CSS minified file

Figure 2 – Getting the minified .css file.

Copy the “bootstrap.min.css” file and paste it in the src folder you have created before.

Then, navigate to the js folder. From there, copy the file called “bootstrap.bundle.min.js“, as illustrated in figure 3.

Like before, make sure to copy the “bootstrap.bundle.min.js” file, highlighted in red, and not the “bootstrap.bundle.min.js.map” file, signaled with a blue cross.

Bootstrap minified js bundle file

Figure 3 – Getting the minified .js file.

Paste the “bootstrap.bundle.min.js” also on the src folder.

Since Bootstrap depends on the jQuery library for some functionalities [2], we will also need to download this library. You can get jQuery from here.

On the download page, select “Download the compressed, production jQuery 3.3.1“, which should basically direct you to this file. Download the file and save it also on the src folder created before.

After finishing, the src folder content should look like figure 4.

ESP32 Arduino src folder for serving bootstrap

Figure 4 – Content of the src folder.

To finalize, we simply need to write the HTML code inside our “index.html” file. The full code is explained in the section below.

After finishing writing the code in the “index.html” file, we need to upload all the files to the ESP32 SPIFFS file system. As already mentioned, we will make use of an Arduino IDE plugin. The procedure to upload files using the plugin is explained in detail here.

In short, we simply need to open the folder of the Arduino sketch where we will write the ESP32 code. To do it, we can click on the “Sketch” menu of the Arduino IDE and select “Show sketch folder“.

Then, we need copy our data folder and paste inside the Arduino sketch folder. Note that we should keep the name “data” since the Arduino IDE plugin will look for a folder named like this and upload all its content to the ESP32 SPIFFS file system

After this we simply need to go to the Arduino IDE and click the Tools menu. There we should choose the “ESP32 Sketch Data Upload” option. Upon choosing that option, the uploading procedure should begin.

Note that the files will keep the paths they had inside the data folder. So, when looking for them in the Arduino code, we should pass the following paths:

  • /index.html
  • /src/bootstrap.bundle.min.js
  • /src/jquery-3.3.1.min.js
  • /src/bootstrap.min.css

Naturally you can use other naming conventions for your folders and files. Nonetheless, as already explained here, the SPIFFS file system doesn’t support directories. Consequently, the whole path of the file will be used as its name.

Since the maximum size of the name of a file in the SPIFFS file system is 31 characters, we need to consider the folder hierarchies and file names carefully to not exceed this limit.

The HTML code

The HTML code for this tutorial will be very simple, just to illustrate that we can use the Bootstrap framework correctly. Recall that this code should be written in the “index.html” file mentioned in the previous section.

So, in the head tag of our HTML page, we will include all the files needed to support the framework. For the JavaScript files, we will use a script tag for each one.

Taking in consideration that the JavaScript files will be retrieved from the same server that is serving the HTML code, we should use a relative path in the src attribute of the script tag.

To include the CSS file we will use a link tag. The href attribute of this tag should also use a relative path.

<head>
	<script src="src/jquery-3.3.1.min.js"></script>
	<script src="src/bootstrap.bundle.min.js"></script>
		<link rel="stylesheet" type="text/css" href="src/bootstrap.min.css">
</head>

On the body of our HTML page we will place the elements needed to render a jumbotron. A jumbotron is one of the many components available in the Bootstrap framework.

The code will be similar to the one from Bootstrap’s jumbotron example, just to illustrate that it is working correctly.

<body>
<div class="jumbotron jumbotron-fluid">
<div class="container">
<h1 class="display-4">Tech Tutorials X</h1>
<p class="lead">Testing Bootstrap.</p>

</div>
</div>
</body>

The complete HTML code can be seen below.

<!DOCTYPE html>
<html>

    <head>
		<script src="src/jquery-3.3.1.min.js"></script>
        <script src="src/bootstrap.bundle.min.js"></script>
			<link rel="stylesheet" type="text/css" href="src/bootstrap.min.css">
    </head>

    <body>
<div class="jumbotron jumbotron-fluid">
<div class="container">
<h1 class="display-4">Tech Tutorials X</h1>
<p class="lead">Testing Bootstrap.</p>

</div>
</div>
</body>

</html>

After finishing the HTML code and saving it, you can do a quick test to make sure it is correctly rendering the component. So, open it with a web browser of your choice. You should get an output similar to figure 5. As can be seen, the component is correctly rendered.

Bootstrap testing jumbotron component locally

Figure 5 – Testing the HTML code.

The Arduino code

We will start the code by doing all the library includes. We will need the following libraries:

  • WiFi.h: allows to connect the ESP32 to a WiFi network;
  • SPIFFS.h: allows to interact with the SPIFFS file system. It makes available the SPIFFS object, which is an extern variable that allows to interact with the file system;
  • ESPAsyncWebServer.h: allows to setup a HTTP web server.
#include "WiFi.h"
#include "SPIFFS.h"
#include "ESPAsyncWebServer.h"

We will also need the credentials of the WiFi network, so later we can connect to it. In particular, we will need the network name and password.

const char* ssid = "yourNetworkName";
const char* password = "yourNetworkPassword";

To finalize the global variables declaration, we will need an object of class AsyncWebServer. This object will be needed to setup the HTTP web server.

Note that the constructor of this class receives as input the number of the port where the server will be listening for incoming requests. We will use port 80, the default HTTP port.

AsyncWebServer server(80);

Moving on to the setup function, we will start by opening a serial connection, so we can later output some content from our program.

Serial.begin(115200);

After that, we will mount the SPIFFS file system, so later we can use it to serve the needed files for the Bootstrap framework. We will perform an error check to make sure the file system was correctly mounted before we proceed.

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

Then we will connect the ESP32 to the WiFi network, using the credentials (network name and password) previously declared as global variables. After the procedure is complete, we will print the local IP assigned to the ESP32, since we will need it later to reach the server from a web browser.

WiFi.begin(ssid, password);

while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi..");
}

Serial.println(WiFi.localIP());

After this we will take care of setting up the web server routes, which will be responsible for serving our web page and the Bootstrap files.

The first route we will configure will be the one serving our HTML web page. We will call it “/index“, like the name of our file.

This route will only listen to HTTP GET requests and its handling function implementation will consist on serving the “/index.html” file stored in the file system.

As we have seen in many of the previous tutorials about the async HTTP web server, the route handling function will receive as input a pointer to an object of class AsyncWebServerRequest.

This class exposes a method called send, which allows to return the answer back to the client. This method is overloaded and has many signatures (you can check them here) that we can leverage for different use cases.

We will use the version of the send method that receives as first input an object of class FS, as second a string with a path to a file in the file system, and as third the content type of the data that will be returned to the client.

So, as first argument we will pass the SPIFFS object. The SPIFFS object is an extern variable of class SPIFFSFS, which inherits from the class FS. The SPIFFS object will be used under the hood by the async web server library to access the files and serve them.

As second argument we will pass the full path of the file in the file system, which is “/index.html“. As third argument, we will pass the content type “text/html“, since we are serving a HTML page.

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

Besides this, we will need to specify a route for each of the files for the Bootstrap framework. The name of the routes should match the relative paths we have used to include the resources in the HTML file.

So, for example, for the script tag that has “src/bootstrap.bundle.min.js” as the src attribute, the route should be called “/src/bootstrap.bundle.min.js“.

The implementation of each of these routes will be equal to what we did for the HTML page. We simply call the send method, passing as first input the SPIFFS object, as second input the path for the corresponding file and as third input the content type.

For the third argument we need to take in consideration that we should use the content type “text/javascript” for the .js files and the content type “text/css” for the .css file.

All routes will only listen to HTTP GET requests.

server.on("/src/bootstrap.bundle.min.js", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(SPIFFS, "/src/bootstrap.bundle.min.js", "text/javascript");
});

server.on("/src/jquery-3.3.1.min.js", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(SPIFFS, "/src/jquery-3.3.1.min.js", "text/javascript");
});

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

To finalize, we need to call the begin method on our server object, so it starts listening to incoming requests.

server.begin();

The final code can be seen below.

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

const char* ssid = "yourNetworkName";
const char* password =  "yourNetworkPassword";

AsyncWebServer server(80);

void setup(){
  Serial.begin(115200);

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

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi..");
  }

  Serial.println(WiFi.localIP());

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

  server.on("/src/bootstrap.bundle.min.js", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(SPIFFS, "/src/bootstrap.bundle.min.js", "text/javascript");
  });

  server.on("/src/jquery-3.3.1.min.js", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(SPIFFS, "/src/jquery-3.3.1.min.js", "text/javascript");
  });

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

  server.begin();
}

void loop(){}

Testing the code

To test the code, simply compile it and upload it to your ESP32, assuming that you have already uploaded the files to the SPIFFS file system of the device.

Once the procedure finishes, open the Arduino IDE serial monitor. The IP address assigned to the ESP32 on the network should get printed. Copy it.

Then, open a web browser of your choice and type the following in the address bar, changing #yourDeviceIp# by the IP you have just copied:

http://#yourDeviceIp#/index

You should get an output similar to figure 6. As can be seen, the page is correctly served and rendered and all the file dependencies are also served by the ESP32 server.

Note that the request that is failing with a 500 status code correspond to the browser request to try to obtain the website’s favicon, which we did not contemplate in our server. Thus, this result is expected.

ESP32 Arduino serving Boostrap

Figure 6 – Web page with Boostrap component served by the ESP32.

References

[1] https://getbootstrap.com/

[2] https://getbootstrap.com/docs/4.3/getting-started/download/

2 Replies to “ESP32 Arduino: Serving Bootstrap”

  1. Hello,
    In line 43, I get the following error message: ‘gt’ was not declared in this scope.

    Kind Regards

    Juergen B.

    1. Hi!

      My blog’s infra structure was migrated and the syntax highlight plugin is broken on the new one 🙁

      where you see < it should be appearing a < character, and where you see > it should be appearing a > character.

      This is true for both the HTML and Arduino code snippets. The easiest way is probably copy the code to a text file and do a find and replace of those characters.

      Sorry for the inconvenience, I’m waiting for the tech support guys to sort it out, but solving this is beyond me at this point 🙁

      Best regards,
      Nuno Santos

Leave a Reply