The world has become hyperconnected. It fundamentally changed the way we communicate, work and entertain ourselves. I grew up with the internet and, despite my terrible bandwidth, I was able to learn from and experience content created by strangers around the globe.

Most people heavily rely on web services. Yet, they don’t know how these systems operate at scale. Among developers too, there is a lack of fundamental understanding of the technologies, and sometimes, a sentiment that we are just following trends.

As storage is getting extremely cheap, the costs associated with hosting a service mostly depend on the server’s CPU and memory usage. Cloud providers make computing resources available on demand in a pay-as-you-go model. Even better, event-driven provisioning reduces costs further, by billing only for resources in use. This is an opportunity for many businesses to scale out with no upfront costs.

In this article, I want to shed some light on the topic of web apps and web services, present their components and touch on some cloud architecture concepts. Disclaimer: this is not a complete guide, and we won’t cover domain specific workloads as they vary a lot across applications.

Table of content

Apps and services

Let’s start with some definitions:

  • A website is a collection of interlinked web pages under the same domain.
  • A web app is software delivered as web pages, executed in a browser. Web apps support server-side and/or client-side processing. Unlike traditional web content, apps include executable elements of interactivity and persistent state.
  • A web service is a software system which communicates with other machines over a network. It is platform/framework-agnostic to offer great interoperability and extensibility. By combining simple services, we can achieve complex operations.

Since the distribution of static content doesn’t require a server running custom code, it is much cheaper to host a static website than to operate a service.

Lately, we have seen a rise in single page applications, which use the browser as environment for running native-like apps. The executables qualify as static content, but they often need to fetch dynamic data from services to perform authentication, get personalized content, etc.

For applications involving content streaming and real-time messaging, WebSockets are used to keep connections open between server and client. Sometimes however, the server is simply a matchmaker in a peer-to-peer network: this is common practice in video conferencing.

Networking

Having some understanding of the network is recommended. We will go through a typical client-server exchange in the Internet protocol suite (TCP/IP), after the client enters a URL:

  • First, if the domain name is not in the computer’s cache, the client will issue a request to the Domain Name System to resolve the IP address of the web server.
  • Then, a route to the server is computed. Routers use a gossip mechanism to find short paths between nodes in the network, in a distributed manner, by maintaining routing tables.
  • A TCP handshake is performed to create a session, opening sockets on both ends for communication. They are generally bound to port 80 for HTTP, or 443 for HTTPS.
  • The client sends an HTTP request, to which the server responds with some content and a code, indicating whether the request was successful. HTTP headers are used by client and server to transmit additional information about the content and the request.

The web relies on the end-to-end design of its applications: the network is operated by small and efficient payload-agnostic components that ensure a reliable delivery of data packets.

In Python, the Web Server Gateway Interface (WSGI) is a specification for a web server to call your application and formulate a response, allowing for more convenient request handling in your code, and building more robust servers. It replaced the Common Gateway Interface, designed without any programming language in mind, and inspired ASGI, which added support for asynchronous calls. For other languages, you might want to look up Rack (Ruby), PSGI (Perl) and JSGI (JavaScript).

Stacks

Solution stacks are sets of components that interoperate to deliver most functionalities a platform requires. Popular stacks have good documentation on how their components fit together:

  • LAMP (Linux, Apache, MySQL, PHP, Perl or Python)
  • MERN (MongoDB, Express.js, React, Node.js)
  • GRAND (GraphQL, React, Apollo, Neo4j Database)

PHP was designed for web development, and it is still going strong, powering today nearly 80% of the web1. Although the language is universally hated, its execution environment is praised for its reliability. PHP (or rather Hacklang) is currently used by WordPress and Wikipedia for content management systems (CMS), but also by Facebook and Slack for building web apps.

As most general-purpose languages have libraries for creating web applications, new developers tend to use the ones they are already familiar with, or which seem easier to learn, like JavaScript and Python.

Web frameworks are used to generate content and to handle interaction server-side. Django and Ruby on Rails are popular ones. They are sometimes criticized for being too opinionated. Lightweight alternatives, such as Flask or Express.js offer more flexibility, and are preferred for small applications.

Databases

An important component of services is the database. Let’s go through the most common and established database paradigms, with corresponding examples:

  • Relational DB (MySQL). These are similar to spreadsheets. “Relations” here are defined per table, between records (rows) and attributes (columns). They support SQL (structured query language) for finding, processing and analyzing the data. Some RDBs support more types (arrays, JSONB) than others and can implement variants of SQL to support advanced queries. Interoperability with object-oriented languages is achieved via object-relational mapping (ORM), and entries in different tables can be cross-referenced using primary and foreign keys.
  • Document store (MongoDB). Relational databases impose all documents of a table to have the same format. This can be a desirable property, but it is less suited for more heterogeneous data. Here, a document can be thought of as a JSON object which can be retrieved with a key. Indexes can be created for finding documents based on their attributes. Document stores are part of a broader category, NoSQL, which often trades query capabilities for better horizontal scalability.
  • File system (AWS S3). For storing media, like users’ profile pictures, it is sufficient to have an addressing system for locating the content. The folder hierarchy can be used for organizing content, and creating path-based access policies.

Cloud databases, provided as-a-service, are a compelling option, especially in case of infrequent reads, as they only charge for storage and per operation, sparing you the cost and maintenance of a dedicated machine.

Other options are available, generally serving more specific needs:

  • Redis is an in-memory key-value store. With all the data in memory, it is capable of sub-millisecond latency and high throughput. It can serve as a cache layer, or message broker. Redis provides a collection of built-in data structures and operations that make it quite versatile.
  • NewSQL DB (TiDB). With SQL, the data is altered through transactions, which should be executed sequentially, or at least result in the same outcome as if they were. We want such databases to be ACID2 compliant to ensure data integrity. NewSQL databases address the scalability issues of relational databases while maintaining ACID guarantees. They are suitable for online transaction processing (OLTP) workloads.
  • Cassandra is a common choice for large platforms, focusing on high availability and scalability. It features a column-wide model which doesn’t impose a structure on the entries. Instead, Cassandra prioritizes data locality and takes partitions into consideration in its data model.
  • Graph based DB (Neo4j). Some applications rely heavily on the relations between entries. Although entries can be cross-referenced, none of the previous options can traverse relation networks as efficiently as a native graph database. In 2018, eBay chose Neo4j for its recommendation engine, to enable real-time updates from contextual information. The technology is promising but still maturing.

Because databases are designed with different applications in mind, picking the right one can be challenging. You need to look out for latency, scalability, and think about how you intend to query it.

Data normalization is generally a good practice: it is better to reference entities than to copy their data. However, data duplication is acceptable in some contexts. For instance, indexes often rely on different data structures, and sometimes require additional transformation of the data (e.g. tokenization and stemming in full-text search). Caches too, store some duplicated data; they help speed up responses, and take some load off the main database.

the monolith

Architecture

When it comes to building apps, we generally distinguish between two styles:

  • Monolithic applications. The simplest way to get an application running, is to put all the code on one machine. Frameworks like Django and Rails are designed to contain all the application logic. Such applications scale by serving replicas behind a reverse proxy. They are not as “trendy” as the next option, but incur less development and operation overhead. Most apps start as a monolith, and only transition to the next model when they have grown too much in complexity.
  • Microservices (previously Service Oriented Architecture). In this model, each service operates autonomously and communicates over the network with the others, using interfaces they agreed upon. Microservices simplify the integration of new features and make the evolution of individual components seamless, as their codebases and deployments are decoupled.

For provisioning, depending on the desired availability, we can have servers constantly running, or allocate computing resources on demand. The second approach is called serverless, and is much cheaper, as we only pay for the execution time. Your application gets shut down when there is no traffic, and can handle short activity spikes by rapidly booting up new instances. However, it suffers from cold starts, meaning the first request to the server can take a few seconds to complete. Serverless design also restricts features: there is no concept of session and data cannot be persisted on the server.

Applications and services can be hosted on a cluster, to gain in modularity while keeping networking costs low. Kubernetes (k8s) is a system for automating deployment, scaling, and management of containerized applications. Applications are packaged in container images, which provide a flexible, lightweight and reliable execution environment.

Interfaces

Services can communicate over the network, among each other or with the client, through interfaces. The services’ responses are generally structured using JSON, XML or protocol buffers. The main API concepts you will encounter are:

  • Remote procedure call. With RPCs, you are exposing functions for the client to call. Such interfaces are tightly coupled with the server capabilities, meaning that it is for the callee to decide how the service should be used. High performance RPCs commonly use gRPC. They are generally avoided for accessing resources, as they lack standardized fetching patterns.
  • REST3 (Representational State Transfer). Designed for resource access, RESTful APIs use the semantics of the HTTP methods POST, GET, PUT, DELETE to respectively create, read, update and delete resources. These CRUD operations should be idempotent, for the client to repeat them in case of a network failure. Such APIs are human readable, and offer sufficient abstraction for the client to understand how they should be called with little documentation.
  • GraphQL. It is a query language for the client. The server translates GQL to query the DB and retrieve the data. It can help save bandwidth, as the client only asks for the fields they are interested in. It also provides a convenient way of making recursive queries, but they have to be implemented efficiently for the server to batch requests to the database (N+1 problem).

Security

HTTPS is an extension of HTTP, which uses an encrypted communication protocol. SSL/TLS certificates are provided by trusted authorities to verify the server’s public key. Encrypted communication is necessary to prevent eavesdroppers from gaining information or impersonating users.

Cookies are a means of transporting information in HTTP headers, and stored by the client. They usually consist of a number, allowing to keep track of a user across sessions.

Authentication is making sure a user is who they pretend to be, and authorization, that they have sufficient rights to access a given resource. Both depend on some data associated with the user, which can be cumbersome to manage. There are two ways of doing it:

  • Storing session information server side. Session data is stored in memory for quick access, and the clients identify themselves with a session-id. This mechanism is widely used, and is very secure over HTTPS, as the authorization logic is handled server side. Its main drawback is horizontal scaling, as the server becomes stateful.
  • Storing information client side with JSON Web Tokens, popular with microservices architectures. One service is responsible for issuing access tokens, which are short lived and not designed to be revoked. JWTs are signed to avoid forgery, and can store additional information, like the scopes the user has access to, which makes it easy for other services to check for authorization. OAuth2 and OpenID Connect are industry-standard protocols based on JWTs.

An interesting strategy used by social media for content access is obfuscation. By generating random URLs for pictures and videos, they ensure only people having the link can access the content.

In order to enforce centralized authorization, a proxy can be used to forward valid requests to the back-end services.

Front-end

Once the core services are set up, we need a visually pleasing and practical interface for the end-user.

The front-end is all about the presentation and how the user, from their device, interacts with the app. In contrast, the back-end refers to services and data access handled by the servers. Web pages interact with the browser using 3 languages (plus Web APIs):

  • HTML, for structure and content. The document object model (DOM) is a rooted tree corresponding to the HTML code. Its nodes represent the displayed elements.
  • CSS, for style and layout. CSS uses selectors to match the elements the styling is applied to. Properties include size, position, colors, fonts, and animations. Media queries and grids are used to design responsive layouts which adapt to the users screen.
  • JavaScript, for executing code client-side. JavaScript has a concurrency model based on an event loop, and encourages non-blocking asynchronous calls. As a multi-paradigm language, JavaScript supports event-driven, functional, and imperative programming styles. ECMAScript (ES) is a standard for the language. Each version adds new features which allow for a more expressive syntax. However, they break compatibility with older browsers. ES6 (2015) was a major redesign of the language, which has now high penetration.

Single page web apps focus on interactivity and have to deal with complex state management. As developers want to write more abstract code, in many cases, the client-side code is generated. This is commonly achieved with the following tools:

  • TypeScript: a superset of JavaScript, which uses type annotations. Types, combined with static analysis of the code, can bring more features to editors: they help catch errors early on, provide documentation and better autocompletion.
  • React: a library for creating responsive web apps. It uses JSX to write components, effectively embedding HTML-like syntax in JS, and updates them as their state changes.

Generally, the generation of HTML is achieved using template engines like Jinja. A practical aspect of PHP is that it can simply be embedded in HTML files, as a tag, to generate the necessary code. Similarly, writing in JSX means that the app developer does not have to mix two or more languages for template rendering, server-side and client-side scripting.

Other notable front-end concepts include:

  • AJAX: a set of techniques for making asynchronous requests. After the page is loaded, additional data is fetched to update the content dynamically. Incremental page loading can be used to increase responsiveness and avoid reloads.
  • Web workers: separate threads running JavaScript. They don’t have access to the DOM, and have to communicate with the main thread via a messaging interface. Web workers allow to perform intensive work, without affecting the responsiveness of the page. Service workers are similar, but act as a client-side proxy, enabling push notifications, background sync and managing local cache for offline scenarios.
  • HTML canvas and WebGL, for rendering complex and interactive 2D and 3D graphics. The canvas API can be used to create vector graphics, animations, and manipulate photos. On the other hand, WebGL is hardware-accelerated and used for 3D scenes and shaders.

Delivery

A few steps are required to turn such apps into a single .js file, shippable to the browser: transpilation to vanilla JavaScript syntax, dependencies resolution, code minification and bundling. Under the hood, create-react-app uses Babel and webpack for this.

As discussed, sites built with tools like React use client-side JS to generate the HTML. However, static websites are easier to cache, and render faster. Next.js is a framework that offers a few ways of rendering content on the server, at build time, or at each request. To the client, the content appears sooner, before running any JS, which is desirable for improving user experience and search engine optimization (SEO).

Content delivery networks (CDNs) aim at providing high availability and low latency distribution of content, through optimized routing and “edge servers” physically close to the clients. These networks consist of proxy servers and data centers. They are used by all major companies for their cacheable content, to reduce server load and latency.

Nowadays, delivery is synonymous with DevOps, and in particular, the practice of CI/CD: merging changes to the main codebase on a regular basis (continuous integration), and making releases of the software (continuous delivery). The software is regularly built and tested, without necessarily deploying each release. Continuous integration prevents teams from creating incompatible components. Cloud repositories provide tools for running CI/CD pipelines on events, like commits to certain branches.

Conclusion

The numerous tools, frameworks, databases and services you will come across reflect the many ways of doing things on the web. Standardized protocols and technologies built two decentralized networks: a physical one for the infrastructure, and a virtual one, connecting websites with hyperlinks.

In short, a web app boils down to client interaction, state management and back-end services. Many services have common concerns like authentication and scalability. Data access and interfaces is what you need to get right; the options discussed above should cover most use cases.

At the time of writing, progress has been made client-side, shifting some logic off the servers, while providing the same convenient declarative coding style PHP first offered. NoSQL recently gained a lot in popularity among developers, but this is likely due to the convenience of the lack of schemas, rather than the scalability offering the model was originally designed for. I think it shows that the available SQL tooling (e.g. SQLAlchemy) is still overly complicated.

With the popularization of HTTP/2.0, I believe streaming and async data fetching to become more straightforward. Graph DBs will probably be overhyped, but will find good use cases. The Jamstack, which I didn’t mention here, is also gaining traction. Companies like Lighthouse and Vercel propose CDN hosting of static websites, which directly interact with back-end APIs or microservices.

Further readings