In the distance, a boat with a fisherman. He caught not a fish, but a mermaid in the foreground. The mermaid is smiling and holding a hook in her hand.

How Web Applications Work

The history of the development of web applications or why we need a backend.

Time to read: over 15 min

Briefly

Web applications that we use every day, although they are very different under the hood, are largely similar at a high level.

In this article, we will look at how web applications work, what client-server architecture is, and what technologies are used for communication between the client and the server.

Any web application that runs in a browser operates on 3 main technologies: HTML, CSS, and JS. There is also WASM, but we will not talk about it in this article.

All the applications you use in the browser: GoogleDocs, Notion, Yandex.Maps, Spotify, YouTube — are made using these technologies.

But any complex application is not just an image in the browser; it also includes the data that users use or create. This data needs to be stored, processed, and displayed.

Storage and processing of data is usually the responsibility of the server or backend.

Backend — the field of web technologies that operate on the server, as well as the internal part of the server system, is responsible for data processing.

The architecture in which the server (backend) and the client (browser, frontend) participate is called client-server.

Client-Server Architecture

Let's start with the concept itself.

Architecture — is a description of the system at the highest level.

That is, when describing architecture, we do not go into the details of each specific module, but rather describe how they interact with each other, "fixing the agreements" on the behavior of each of them.

Client-server architecture describes how the client (in our case, the frontend) and the server (backend) interact with each other.

The most canonical form of such architecture looks like this:

client-server architecture diagram

Client

The client makes requests to the server.

Requests can vary. The client may ask the server to return some data or ask to prepare it in some way before returning it. Or it may ask the server to save something it passes along with the request.

The client's role for the server is to inform the server what to do with the data stored in the database, or with the data it is passing.

The client's role for the user is to present the data in a user-friendly manner and provide mechanisms for updating it.

For the web, the client is almost always a browser.

Server

The server receives requests from the client.

Its role is to store information from the client in a database, process it, and provide access to it according to certain rules. Such rules are usually called business logic.

On the server, in addition to communicating with the client, some background tasks may run, such as indexing information in the database for faster search or sending automated email newsletters.

Database

A database (DB) is a storage for all user and service information.

Its role is to ensure fast and uninterrupted access to this information and to store it.

Example

Let's take Twitter. In this application, the client will be its entire frontend: HTML pages, CSS styles, JS scripts for user interaction with the UI.

The server will be its backend. All technologies used to provide information from databases to the client via API.

Databases (in plural, because there are many) will be all technologies responsible for storage, duplication, backup of data, and ensuring uninterrupted operation.

Development of Web Applications

Most applications are built on client-server architecture. However, the first applications did differ a bit from today's in implementation details. The main difference was a complete page reload in response to any action.

First Applications

The very first web applications did not differ much from ordinary websites.

Essentially, they were websites because all business logic was implemented on the server. The client only displayed the results as HTML pages to the user and sent requests for other pages.

A simple choice of sorting a list or table required a page reload. It seems anachronistic now, but early applications operated exclusively this way simply because browsers lacked tools for alternative communication.

In the early 2000s, the so-called "Web 2.0" appeared, bringing AJAX with it.

AJAX

AJAX (Asynchronous JavaScript and XML) — communication between the client and the server without reloading the page.

When we say that an application uses AJAX, we mean that when clicking, for example, the sort button on a list or table, the page will not reload, and the sorting will happen "in the background."

When clicked, the browser sends a request to the server, indicating that it is necessary to sort a certain list by a certain criterion.

The server will process the request, retrieve the required data, sort it, and send the client a ready piece of interface that the client will insert into the document.

Notice that we still do not transmit data in raw form. Now on the client, we receive a piece of HTML markup that we then embed in the appropriate place in the document.

This is undoubtedly more convenient for the user than reloading the entire page, but it is still not the ideal option for developers.

Modern Applications

In modern applications, communication between the client and server is based on data rather than rendered pieces of markup. More often than not, JSON is chosen for this communication.

Currently, most applications work like this:

  1. The client makes the initial request to the server.
  2. The server responds with an HTML page, sometimes with a set of data embedded as a JS object at the end of the page.
    1. The page may either be rendered on the server, and then the client receives the ready HTML.
    2. Or rendering may be done by the client using some library, such as React. In this case, the server simply returns a set of necessary data.
  3. The user performs some action, such as asking to sort a table.
  4. In response to this action, the client decides which request to send to the server, constructs this request, and sends it.
  5. The server receives this request, processes it, and sends a portion of new data back to the client.
  6. The client receives the data and re-renders a portion of the page based on it. That is, it no longer replaces one piece of markup with another ready-made piece but draws the markup itself.

The advantages of such communication (where only data is transmitted) are several:

  • The server and the client become independent of each other. The server may not need to know anything about the structure of the pages; it only needs to work with the DB and process data (initial rendering may be done by the server using SSR).
  • The amount of information that needs to be transmitted and received is less — which reduces traffic volume.
  • The application logic on the server can be simpler since the client and server become less dependent on each other in terms of data format.

Separation of Concerns

This communication between the server and the client closely resembles the MVC pattern.

MVC (Model-View-Controller) — is an application structure in which three different entities are responsible for data, its processing, and its output.

  • The model (model) is responsible for data and its structure.
  • The view (view) — for their display.
  • The controller (controller) — for their processing.
Model View Controller pattern diagram

If we try to compare a classic client-server architecture with MVC, we can compare the DB with the model, the server with the controller, and the client with the view.

In the earliest applications, this was essentially the case. However, now, the client has become more complex, so MVC may be part of the architecture of the client application.

MVC on the Client

Let's take Notion as an example. Its web version works directly in the browser, but it is a full-fledged text editor.

Most of the work in entering text takes place on the client. For such complex applications to function and for developers to understand their code, client-side code is also designed according to rules and best architectural practices.

One such practice is the separation of data and presentation.

How a Text Editor Would Have Been Made 20 Years Ago

If we forget about the impossibility of saving written text on the server, it was still possible to try to write a text editor back then. But the result would likely have been a chunk of markup with inline styles and text inside.

Working with such data, which is intermixed with presentation features, is inconvenient and sometimes impossible.

Text Editors Today

...most commonly, they are made by separating data from their presentation.

This way, we no longer need to find the text and clean it of styles, because it contains only text and some of its attributes.

  1. This makes working with the data much simpler — we can export the document in different formats: .md, .html, .txt.
  2. It is easier to work with this representation in the code — which makes the code itself simpler to understand.
  3. It is more convenient to store it in memory as application state.

Application State

The text editor in our example above is an application with state.

Application state is all the data of this application at the current moment.

In the case of a text editor — it is all the text that the user has entered, as well as the results of transformations on this text.

Of course, text editors are not the only type of such applications. Any application that stores something before sending (or even without sending) to the server is an application with state.

The advantages of state separation are the same:

  • data cease to depend on how we want to represent them;
  • they are easier to process and store;
  • presentation is easier to change.

Typically, state in JS applications is some object or array of objects in memory.

        
          
          // For example, this could look like// the state of a text editor:const State = {  lastModified: '2020-08-24T18:15:00',  blocks: [    {      type: 'heading',      data: {        text: 'Some heading',      },    },    {      type: 'paragraph',      data: {        text: 'Some paragraph of text under the heading',      },    },  ],}
          // For example, this could look like
// the state of a text editor:
const State = {
  lastModified: '2020-08-24T18:15:00',
  blocks: [
    {
      type: 'heading',
      data: {
        text: 'Some heading',
      },
    },
    {
      type: 'paragraph',
      data: {
        text: 'Some paragraph of text under the heading',
      },
    },
  ],
}

        
        
          
        
      

This form of state is not only convenient to use within the application, but also simple to save using JSON.

Furthermore, applications where a separate module is responsible for state are easier to develop and modify.

There are many approaches and tools available now for state management. Among the most popular are Redux (and those based on its approach, such as Vuex, NgRx), MobX, Overmind.

JSON

JSON is one of the most popular data formats. It is succinct, understandable to both humans and computers, and many languages already know how to work with it.

In the web, JSON is, you could say, the standard because it is used as the default format in many frameworks.

The state from the example above in JSON format would look like this:

        
          
          {  "lastModified": "2020-08-24T18:15:00",  "blocks": [    {      "type": "heading",      "data": {        "text": "Some heading"      }    },    {      "type": "paragraph",      "data": {        "text": "Some paragraph of text under the heading"      }    }  ]}
          {
  "lastModified": "2020-08-24T18:15:00",
  "blocks": [
    {
      "type": "heading",
      "data": {
        "text": "Some heading"
      }
    },
    {
      "type": "paragraph",
      "data": {
        "text": "Some paragraph of text under the heading"
      }
    }
  ]
}

        
        
          
        
      

...and we could directly send it in this form to the server to save it in the DB.

Requests and Responses

As we know, client-server architecture is built on requests and responses. Let's figure out what the browser uses to send a request to the server.

fetch()

At the application level, we use the built-in browser API, specifically fetch(). This is a global method for sending requests.

In the example above, to save the state on the server, we would use it like this:

        
          
          const response = await fetch('/api/save-text', {  method: 'POST',  body: JSON.stringify(State),})
          const response = await fetch('/api/save-text', {
  method: 'POST',
  body: JSON.stringify(State),
})

        
        
          
        
      

Here we send a request to the address /api/save-text with the body JSON.stringify(State). The address and the body (data) are clear, but what is method: 'POST'?

HTTP Methods

The method field is known as an HTTP method.

HTTP method because its values can only be GET, POST, PATCH, PUT, DELETE, and a few other less commonly and explicitly used methods.

They indicate what action we want to perform. In the example, method: 'POST' will inform the server that we want to create a new resource and save the contents of body within it.

If we wanted to retrieve some resource, we would use GET. For editing — PATCH, for replacement — PUT, and for deleting — DELETE.

HTTP method because it is used in the HTTP protocol — the data transmission protocol that browsers use by default when communicating with a server.

We will not describe the protocol itself, but we have an article “HTTP Protocol”.

REST

Observant readers may have noticed that the list of HTTP methods above resembles a list of methods from a RESTful API.

REST (Representational State Transfer) — is a style of communication between components, where all necessary data is specified in the request parameters.

The distinguishing feature of this style is the URL construction style and method selection.

        
          
          // For example, the method and URL for creating a user// might look like this:// POST /api/users// To get a specific user:// GET /api/users/1// (Where 1 is the user ID.)// For editing user data:// PATCH /api/users/1// For deleting data:// DELETE /api/users/1
          // For example, the method and URL for creating a user
// might look like this:
// POST /api/users

// To get a specific user:
// GET /api/users/1
// (Where 1 is the user ID.)

// For editing user data:
// PATCH /api/users/1

// For deleting data:
// DELETE /api/users/1

        
        
          
        
      

As we can see, the base of the URL does not change, while the desired action is expressed by the method.

The lists of HTTP methods and REST methods indeed look similar because REST is based on HTTP 1.0. The method is one of the parameters of a REST request.

Currently, many services and applications operate based on REST.

The Development of the Web and Data Volumes

With the development of the web and the internet as a whole, came the time of large data volumes. Images of 10 MB are no longer surprising.

Along with large volumes came problems with their transmission because traffic was initially very expensive. It is still not cheap in some cases.

Caching

Partially, the solution was the caching of resources. This helped with images — cached images do not consume traffic, as they are not fetched over the network.

For scripts and styles — it helped partially, because volume is only part of the problem. The second part is parsing and execution. This issue is particularly acute with scripts because 10 MB scripts are also not uncommon.

Now developers are striving to reduce the amount of code that the browser needs to download and execute to make applications faster and lighter.

Code Splitting

One solution is code splitting. This is a technique where only the code necessary for a specific page is included in the script, while the rest of the code is loaded as needed.

Middle-end

Caching and code splitting solve most problems; however, there is still one area where we may transmit too much unnecessary information — the requests themselves.

REST is known not only for its convenience but also for its (sometimes) excessive verbosity.

The problem is that REST systems are designed to change as little as possible when the client changes. As a result, the data returned in the response may include details that the client will not use.

Simply removing them often proves impossible, as it contradicts backward compatibility. Generating many similar endpoints that differ only slightly is costly and impractical.

Thus, the community is currently looking towards a sort of middle-end — a layer between the client and the server (or directly the DB) that would remove all unnecessary information not required by the client and provide only the necessary data, thus reducing the volume, like GraphQL.

Conclusion

In this article, we have only covered the fundamentals of how web applications work without getting into the details.

We learned about client-server architecture, the separation of data from presentation, and how they are used in modern applications. We also discussed what application state is and talked about REST as one of the common ways to build APIs.

These concepts will help you understand how most of the applications you may encounter work.