DEV Community

Cover image for I Built a Web Page with C! Here’s How WebForms Core Makes It Possible
Elanat Framework
Elanat Framework

Posted on

I Built a Web Page with C! Here’s How WebForms Core Makes It Possible

C is one of the most influential programming languages in software development. It is widely used for operating systems, embedded systems, networking, infrastructure, and performance-critical software.

But can C also be used to build an interactive web application where the server controls the HTML interface?

Yes. And the server can control the HTML interface without requiring a separate frontend application.

WebForms Core (WFC) is a server-side UI manipulation technology developed by Elanat. It allows a server application to generate commands that manipulate the HTML DOM in the browser.

The basic architecture is:

C Server
    ↓
WebForms header
    ↓
Commands
    ↓
WebFormsJS
    ↓
HTML DOM
Enter fullscreen mode Exit fullscreen mode

This article shows how to build a web application in C using Mongoose as the HTTP server and WebForms Core for server-side UI manipulation.


WebForms Core in C

The C implementation is provided as a single header file, WebForms.h.

This makes integration straightforward. The application does not need a specific C package manager for WebForms Core.

Web Programming With C

For the HTTP server, this example uses Mongoose:

#include "mongoose.h"
#include "WebForms.h"
Enter fullscreen mode Exit fullscreen mode

Mongoose handles HTTP requests and responses, while WebForms Core generates commands for manipulating the browser interface.

The resulting stack is:

C Application
    │
    ├── Mongoose
    │      └── HTTP Server
    │
    └── WebForms Core
           └── UI Commands
                  ↓
              WebFormsJS
                  ↓
              HTML DOM
Enter fullscreen mode Exit fullscreen mode

Download the mongoose.h and mongoose.c files directly from the link below:

https://github.com/cesanta/mongoose

The C WebForms header is available in the WebForms Core GitHub repository:

https://github.com/webforms-core/Web_forms_classes/tree/elanat_framework/c

You can get WebForms.h directly from the c directory and place it in your C project.

This makes the WebForms Core C implementation easy to integrate into projects using different build systems or development environments.


Building the Web Page in C

The HTML page can simply be stored as a C string:

static const char *view_html =
"<!DOCTYPE html>\n"
"<html>\n"
"<head>\n"
"  <title>Using WebForms Core</title>\n"
"  <script type=\"module\" src=\"/script/web-forms.js\"></script>\n"
"</head>\n"
"<body>\n"
"    <form method=\"post\" action=\"/\">\n"
"        <label for=\"txt_Name\">Your Name</label>\n"
"        <input name=\"txt_Name\" id=\"txt_Name\" type=\"text\" />\n"
"        <br>\n"
"        <label for=\"txt_FontSize\">Set Font Size</label>\n"
"        <input name=\"txt_FontSize\" id=\"txt_FontSize\" type=\"number\" />\n"
"        <br>\n"
"        <label for=\"txt_BackgroundColor\">Set Background Color</label>\n"
"        <input name=\"txt_BackgroundColor\" id=\"txt_BackgroundColor\" type=\"text\" />\n"
"        <br>\n"
"        <input name=\"btn_SetBodyValue\" type=\"submit\" value=\"Click to send data\" />\n"
"    </form>\n"
"</body>\n"
"</html>";
Enter fullscreen mode Exit fullscreen mode

There is no JSX, no frontend component framework, and no separate frontend application.

The page is standard HTML.


Controlling the HTML from C

When the form is submitted, Mongoose provides the POST data:

char fontSize_str[32] = {0};
char background_color[128] = {0};
char name[128] = {0};

mg_http_get_var(
    &hm->body,
    "txt_FontSize",
    fontSize_str,
    sizeof(fontSize_str)
);

mg_http_get_var(
    &hm->body,
    "txt_BackgroundColor",
    background_color,
    sizeof(background_color)
);

mg_http_get_var(
    &hm->body,
    "txt_Name",
    name,
    sizeof(name)
);
Enter fullscreen mode Exit fullscreen mode

A WebForms instance can then be created:

WebForms *form = webforms_create();
Enter fullscreen mode Exit fullscreen mode

The server creates InputPlace selectors:

char *ipForm = webforms_ip_tag("form");
char *ipH3   = webforms_ip_tag("h3");
char *ipBtn  = webforms_ip_tag("btn_SetBodyValue");
Enter fullscreen mode Exit fullscreen mode

And generates UI commands:

webforms_set_font_size(form, ipForm, fontSize);

webforms_set_background_color(
    form,
    ipForm,
    background_color
);

webforms_set_disabled(
    form,
    ipBtn,
    true
);

webforms_add_tag(
    form,
    ipForm,
    "h3",
    ""
);

webforms_set_text(
    form,
    ipH3,
    welcome
);
Enter fullscreen mode Exit fullscreen mode

The C server is therefore not simply returning data. It is generating instructions for the browser.

Screenshot of page load

WebForms Core HTML Load

Screenshot after send data

WebForms Core Change HTML


Low-Overhead Server Commands

One of the interesting ideas behind WFC is that the server sends commands, rather than continuously sending a representation of the entire UI.

The commands are compact and can be embedded inside HTML comments:

Commands with html

...
<b> ... </b>
...
<div> ... </div>
...
<!--[web-forms]
st<h1>=WebForms Core in C!
bc<body>=lightblue-->
Enter fullscreen mode Exit fullscreen mode

This has two useful properties.

First, the commands do not interfere with the visible HTML structure.

Second, the command representation has relatively low overhead because only the required UI operations need to be transmitted.

Commands without html

[web-forms]
fs<form>=26px
bc<form>=cadetblue
sd<btn_SetBodyValue>=1
nt<form>=h3
st<h3>=Welcome Adriano!
Enter fullscreen mode Exit fullscreen mode

WebFormsJS detects the commands and executes them against the DOM.

Server
   ↓
Compact Commands
   ↓
WebFormsJS
   ↓
DOM Operations
Enter fullscreen mode Exit fullscreen mode

A Different Approach to Stateless Server-Driven UI

Before WebForms Core, different systems and approaches attempted to combine server-side UI control with a stateless server and command-based communication.

This model has an obvious challenge:

How can multiple asynchronous interactions remain synchronized without requiring the server to maintain the complete client state?

WFC takes a different approach.

Several mechanisms work together to control the execution flow.

Queue Manager

Requests can be placed into a queue and processed serially.

Request 1
   ↓
Request 2
   ↓
Request 3
Enter fullscreen mode Exit fullscreen mode

This helps preserve the order of UI operations.

Loader

When an operation depends on a server response, a Loader can temporarily lock the relevant interface before the result arrives.

This prevents the user from triggering conflicting interactions while the required server operation is still pending.

Serial Request Processing

Queued requests are processed in order, helping dependent UI operations execute in a predictable sequence.

DebounceDelay

For events that can fire repeatedly, DebounceDelay can prevent unnecessary requests from being generated too rapidly.

Together, these mechanisms address an important problem in interactive Server-Driven UI systems: synchronization between user actions, server responses, and client-side command execution.


Stateless by Design

WFC is, by itself, a stateless server-side UI architecture.

The server does not need to maintain a complete representation of the browser DOM.

A typical flow can therefore remain:

HTTP Request
     ↓
C Server
     ↓
WebForms Commands
     ↓
HTTP Response
     ↓
Browser
Enter fullscreen mode Exit fullscreen mode

However, Stateless does not mean that server-side state is forbidden.

An application can still maintain state on the server when a particular page or scenario requires it.

The choice depends on the application's requirements.


Memory Management in C

Because this implementation is written in C, memory management is explicit.

For example:

WebForms *form = webforms_create();
Enter fullscreen mode Exit fullscreen mode

creates a WebForms instance, while:

webforms_clean(form);
webforms_free(form);
Enter fullscreen mode Exit fullscreen mode

cleans and releases it.

InputPlace values returned by:

webforms_ip_tag("form");
Enter fullscreen mode Exit fullscreen mode

are heap-allocated strings and therefore must also be released:

free(ipForm);
free(ipH3);
free(ipBtn);
Enter fullscreen mode Exit fullscreen mode

The response returned by:

char *response = webforms_response(form);
Enter fullscreen mode Exit fullscreen mode

is also heap-allocated and must be released after sending it:

free(response);
Enter fullscreen mode Exit fullscreen mode

This keeps the API compatible with the explicit memory-management model expected in C.


Running the C Web Server

Mongoose can listen for HTTP connections with:

mg_http_listen(
    &mgr,
    "http://127.0.0.1:8080",
    fn,
    NULL
);
Enter fullscreen mode Exit fullscreen mode

The event loop is then kept alive with:

for (;;) {
    mg_mgr_poll(&mgr, 1000);
}
Enter fullscreen mode Exit fullscreen mode

The application can be opened at:

http://127.0.0.1:8080/
Enter fullscreen mode Exit fullscreen mode

WebFormsJS can also be served directly by Mongoose.


C as a Web Programming Language

The complete stack is surprisingly small:

C
+
Mongoose
+
WebForms Core
+
WebFormsJS
+
HTML
Enter fullscreen mode Exit fullscreen mode

There is no requirement for React, JSX, a frontend build system, or a separate frontend application.

The C application handles the server logic.

WebForms Core generates UI commands.

WebFormsJS executes those commands.

The browser remains the final place where the HTML DOM is manipulated.


Conclusion

Building an interactive web application in C does not require turning C into a frontend language.

Instead, C can remain the server-side language while WebForms Core provides a command-based mechanism for controlling the browser UI.

The architecture is:

C Server
    ↓
WebForms
    ↓
Commands
    ↓
WebFormsJS
    ↓
HTML DOM
Enter fullscreen mode Exit fullscreen mode

WFC combines this architecture with compact commands, HTML-comment command embedding, queue management, serial request processing, Loader controls, and event debouncing.

The result is a server-oriented, stateless-by-design approach to interactive web UI, while still allowing applications to keep server-side state when a specific scenario requires it.

And yes, this entire example starts with a language that was originally designed decades ago for systems programming:

C.

Related links

In Elanat:

in GitHub:

Top comments (0)