This tutorial goes over how to create an access point and serve web pages from an esp32 ☁️!
Supplies
- (1) esp32

Code
For this project, we are using the following libraries:
- Wifi.h (built-in)
- WebServer.h (built-in provided by espressif with the esp32 firmware).
- SPIFFS.h (built-in)
#include <WiFi.h>
#include <WebServer.h>
// SSID & Password
const char* ssid = "ArduinoWebServer";
const char* password = "123456789";
// IP Address details
IPAddress local_ip(192, 168, 1, 1);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
WebServer server(80);
void setup() {
Serial.begin(115200);
// Create SoftAP
WiFi.softAP(ssid, password);
WiFi.softAPConfig(local_ip, gateway, subnet);
Serial.print("Access point available to connect to: ");
Serial.println(ssid);
server.on("/", handle_root);
server.begin();
Serial.println("HTTP server started");
delay(100);
}
void loop() {
server.handleClient();
}
String HTML = "<!DOCTYPE html>\
<html>\
<body>\
<h1>Arduino ESP32 Web Server ☁</h1>\
</body>\
</html>";
// Handle root url (/)
void handle_root() {
server.send(200, "text/html", HTML);
}
If you are uploading this via Arduino IDE, you might see a timeout. An easy way to fix this is to press the boot button on the ESP32 when trying to upload.
Once this has been uploaded, you should be able to see the following in the serial monitor output:

When connecting to the access point via your computer or your phone, you should see the following once you navigate to 192.168.1.1
via your browser after connecting:

There you go! We can successfully serve web traffic when a device connects to the ESP32 and then navigates to 192.168.1.1
!
Writing any application or serving just a web page will not be enough for working on projects. With the ESP32, we have access to SPIFFS; this is built-in and provided by espressif, which gives you access to the filesystem.
Serving from the file system
To serve things from the file system, getting things on the file system for the ESP32 is the first requirement. Fortunately, there is a plugin that allows this for the Arduino IDE.
If you are using a esp32-c3 or esp-s2 please use the patched plugin as the one mentioned previously does not work with those modules https://github.com/lorol/arduino-esp32fs-plugin
Following the instructions on the README will allow you to have a new option in the tools dropdown called ESP32 Sketch Data Upload
menu item. Once the plugin is installed, creating a folder in your sketch directory called data
will be transferred to the ESP32's file system when executed.
Note that this tool works with the Java IDE for Arduino and is currently an open issue with the rewrite for Arduino IDE 2.0
Creating an index.html
in the data directory with the contents of:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Arduino Web Server ☁</title>
<style>
html {
font-size: 62.5%;
}
body {
font-size: 1.4rem;
}
h1 {
font-size: 2.4rem;
}
.container__item {
margin: 0 auto 40px;
}
.landing-page-container {
width: 100%;
min-height: 100%;
height: 90rem;
background-image: url("https://s29.postimg.org/vho8xb2pj/landing_bg.jpg");
background-repeat: no-repeat;
background-size: cover;
background-position: bottom;
overflow: hidden;
font-family: "Montserrat", sans-serif;
color: #09383E;
}
.content__wrapper {
max-width: 1200px;
width: 90%;
height: 100%;
margin: 0 auto;
position: relative;
}
.ellipses-container {
width: 25rem;
height: 25rem;
border-radius: 50%;
margin: 0 auto;
position: relative;
top: 10.5rem;
}
.ellipses-container .greeting {
position: absolute;
top: 7rem;
left: 4rem;
right: 0;
margin: 0 auto;
text-transform: uppercase;
letter-spacing: 2rem;
font-size: 0.15rem;
font-weight: 400;
opacity: 0.5;
line-height: 4em;
}
.ellipses-container .greeting:after {
content: "";
width: 0.3rem;
height: 0.3rem;
border-radius: 50%;
display: inline-block;
background-color: #0C383E;
position: relative;
top: -0.65rem;
left: -5.05rem;
}
.ellipses {
border-radius: 50%;
position: absolute;
top: 0;
border-style: solid;
}
.ellipses__outer--thin {
width: 100%;
height: 100%;
border-width: 1px;
border-color: rgba(9, 56, 62, 0.1);
-webkit-animation: ellipsesOrbit 15s ease-in-out infinite;
animation: ellipsesOrbit 15s ease-in-out infinite;
}
.ellipses__outer--thin:after {
content: "";
background-image: url("https://s29.postimg.org/5h0r4ftkn/ellipses_dial.png");
background-repeat: no-repeat;
background-position: center;
top: 0;
left: 0;
bottom: 0;
right: 0;
position: absolute;
opacity: 0.15;
}
.ellipses__outer--thick {
width: 99.5%;
height: 99.5%;
border-color: #09383E transparent;
border-width: 2px;
transform: rotate(-45deg);
-webkit-animation: ellipsesRotate 15s ease-in-out infinite;
animation: ellipsesRotate 15s ease-in-out infinite;
}
.ellipses__orbit {
width: 2.5rem;
height: 2.5rem;
border-width: 2px;
border-color: #09383E;
top: 5rem;
right: 6.75rem;
}
.ellipses__orbit:before {
content: "";
width: 0.7rem;
height: 0.7rem;
border-radius: 50%;
display: inline-block;
background-color: #09383E;
margin: 0 auto;
left: 0;
right: 0;
position: absolute;
top: 50%;
transform: translateY(-50%);
}
@-webkit-keyframes ellipsesRotate {
0% {
transform: rotate(-45deg);
}
100% {
transform: rotate(-405deg);
}
}
@keyframes ellipsesRotate {
0% {
transform: rotate(-45deg);
}
100% {
transform: rotate(-405deg);
}
}
@-webkit-keyframes ellipsesOrbit {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
@keyframes ellipsesOrbit {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
</style>
</head>
<body>
<div class="container">
<div class="container__item landing-page-container">
<div class="content__wrapper">
<div class="ellipses-container">
<h2 class="greeting">Hello from Arduino</h2>
<div class="ellipses ellipses__outer--thin">
<div class="ellipses ellipses__orbit"></div>
</div>
<div class="ellipses ellipses__outer--thick"></div>
</div>
</div>
</div>
</div>
</body>
</html>
And also updating our sketch to include the SPIFFS
code below;
#include <WiFi.h>
#include <WebServer.h>
#include <SPIFFS.h>
// SSID & Password
const char* ssid = "ArduinoWebServer";
const char* password = "123456789";
// IP Address details
IPAddress local_ip(192, 168, 1, 1);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
WebServer server(80);
void setup() {
Serial.begin(115200);
if(!SPIFFS.begin(true)){
Serial.println("An Error has occurred while mounting SPIFFS");
return;
}
// Create SoftAP
WiFi.softAP(ssid, password);
WiFi.softAPConfig(local_ip, gateway, subnet);
Serial.print("Access point available to connect to: ");
Serial.println(ssid);
server.on("/", handle_root);
server.begin();
Serial.println("HTTP server started");
delay(100);
}
void loop() {
server.handleClient();
}
void handleNotFound() {
String message = "File Not Found\n\n";
message += "URI: ";
message += server.uri();
message += "\nMethod: ";
message += (server.method() == HTTP_GET) ? "GET" : "POST";
message += "\nArguments: ";
message += server.args();
message += "\n";
for (uint8_t i = 0; i < server.args(); i++) {
message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
}
server.send(404, "text/plain", message);
}
bool loadFromSPIFFS(String path) {
String dataType = "text/html";
Serial.print("Requested page -> ");
Serial.println(path);
if (SPIFFS.exists(path)){
File dataFile = SPIFFS.open(path, "r");
if (!dataFile) {
handleNotFound();
return false;
}
if (server.streamFile(dataFile, dataType) != dataFile.size()) {
Serial.println("Sent less data than expected!");
}else{
Serial.println("Page served!");
}
dataFile.close();
}else{
handleNotFound();
return false;
}
return true;
}
// Handle root url (/)
void handle_root() {
loadFromSPIFFS("/index.html");
}
Uploading the new sketch and then running the tool to upload the index.html in the data directory ESP32 Sketch Data Upload
will yield the following results when visiting 192.168.1.1
.

The full sketch can be found at https://github.com/gabrielcsapo/gabrielcsapo.com/tree/main/arduino-web-server-esp-32/sketch.
If you have any questions or want to see more examples in these articles, please comment below or create an issue at https://github.com/gabrielcsapo/gabrielcsapo.com.