Is a server/client design the right choice for your project? We discuss structure and components of this architecture to help you find out, and set you on your server development journey.


The client-server architecture is one of the cornerstones of web development, and is also used for software operating locally (like for most database systems).

Is it the right design for your next project? This post will help you find out, and guide you through the choices to make when creating a basic server application.

In future installments, we will also explore a program of this type in the field, to understand how to build one in practice.

Client-server architecture

The fundamental idea of a client-server system is to divide the task of communicating with the user from the task of performing business logic.

These tasks are usually assigned to different sections of the same program: a client-server architecture goes a step further, with the two being performed by separate programs.

The first of these is called server (also known as backend): it performs business logic and manages resources the user would like to access (e.g., databases). This program does not communicate directly with the user, but exposes an application program interface (API), a set of “locations” to which other programs can send HTTP requests to send and receive data.

The server pairs up with a client (or frontend), which communicates with the user and sends his requests to the server via the API, displaying the data received in response.

In a project following server/client design, the backend (which provides the service) is separated from the frontend (where the service is consumed by the user), and the API mediates between the two.

Is a server/client design the right choice for your project?

The best way to answer is to consider its strengths, and decide the importance that they have for your product.

One of the first things to consider is that a single server (linked to a centralized resource) can manage the requests of multiple clients. In contrast, a monolithic application requires both the server-like and the client-like functionalities and resources to be shipped together.

This is of course fundamental in web programming: for instance, a website can only have one, or a few copies, of a login database, and cannot (and should not) distribute it to all its potential users. The same principle also applies for local software: a single database server can serve any program on your computer requiring data storage, without needing reinstalling for every client.

Another big advantage is encapsulation: since the API mediates all requests, no knowledge of the inner workings of the server are required to write a client (indeed, all you need is to understand the API).

This allows massive extension possibility for this type of software: clients and interfaces can be added freely without recompiling or integrating server code. Indeed, server developers may not even know which client will access the server, decide to swap it later, or use more than one simultaneously.

Server-client architectures also allow smart tooling: since server and client are essentially independent, and only need to communicate with each other via HTTP, it is very easy to build them with different tools.

This allows to choose the optimal tool for each job, without worrying about issues like language interoperability. For instance, many web interfaces are written in languages (such as Javascript) which are very different from those of the servers they connect to (such as Python, C, C++, or Rust, depending on choice and performance requirements).

If these advantages are important for you, a server-client architecture will probably serve you better than a monolithic program. This will usually be the case in web programming, while for local applications it will depend on the situation.

Server Design and Components

If you think that a server/client design is the right choice for your project, you will have to design both components: this guide will mostly focus on the server side, while offering some suggestions to build simple but powerful clients.

As a start, you will have to understand the elements that your server will need, and choose the right components to implement them, based on your requirements. The basic operations a servers will need to deal with will be:

  • Storing data,
  • Retrieving it based on some query parameters, and
  • Communicating with other programs to relay the retrieved data,

which leads to four choices to make:

  1. Which language to use for the project (based on performance and scalability needs);
  2. Which database system to use to store and retrieve the data;
  3. Whether to interact with the database natively or via ORM (see below);
  4. Which framework to use to build the API.

The rest of this guide will explore possible options for these design aspects. No one-size-fits-all solution exists, and you will have to choose the best tools for you based on what you plan to build.

Language

As a rule of thumb, code written in interpreted languages (e.g., Python, Javascript) will usually be slower than equivalent code written in compiled languages (e.g., C, C++, Rust). As a result,

  • Compiled languages are usually preferred where performance is key (e.g., very high number of requests to manage, large data sets).
  • Interpreted languages are a good choice otherwise, since they usually allow faster development.

In general, a priori performance considerations may be tricky, since most server code may actually not be written in the language of choice (or by the developer at all). Indeed, most server software makes use of optimized libraries to perform common operations, for simplicity, security, and performance.

Database System

The first question to ask when selecting a database system is if it should be relational or non-relational.

Relational databases organize data in tables, whose rows are associated to the stored items, and whose columns are associated the fields which describe said items.

For example, if one needs to classify domestic expenses, one may define them by a date, a type (a low-level classification, like “food” or “leisure”), an amount and a description. An example table may then look like

id |    date    | type    | amount | description 
---+------------+---------+--------+-------------
 1 | 2023-11-11 | food    | -10.00 | hamburger
 2 | 2023-11-12 | leisure |  -5.00 | drinks out

(having a unique ID is not necessary, but comes in handy for several database operations). To build such tables, one needs to know the model of the data, i.e., the fields.

By contrast, non-relational (also called document) databases make no assumptions on the model of the data, and can indeed store together data with different models. Practically, this is achieved by storing every entry as a document, usually in JSON format:

{
  "id": 1,
  "date": 2023-11-11,
  "type": "food",
  "amount": -10.00,
  "description": "hamburger"
}

Documents with different JSON keys may be stored in the same non-relational database.

By and large,

  • Relational databases usually offer better performance if the data model is known and fixed.
  • Document databases are a better choice if the model is unknown or variable, and are usually optimized for text searches on document fields.

Once you have selected the right database type for you, you should select the right database service. In the relational case, these come in two shapes:

  • File-based (e.g., SQLite), where data is stored in a file, and the database program is called on-demand to open it and access its data.
  • Service-based (e.g., MySQL or PostgreSQL), where the database program is a server, permanently running on the system. Other processes can communicate with the database server via HTTP requests, to query and receive data.

In general, file-based databases are simpler to deploy and more portable, while service-based ones offer greater performance at scale and versatility.

ORM

Object-relational mapping (ORM) is a technique which allows to use object-oriented programming idioms to manipulate database data. The alternative approach is to use relational idioms, by passing SQL-like instructions (sanitized to avoid security issues) directly to the database engine.

ORM offers several advantages, due to its integration with the language. For instance,

  • You can exploit the data model definition to define simultaneously data classes (for the program) and tables (for the database), and leverage deeper integration between the two.
  • ORM frameworks also automatically take care of security issues such as SQL injections (unless raw SQL commands are explicitly passed, in which case the responsibility is on the user).

Lower-level interfaces also have some advantages:

  • They may be more expressive in some cases (and indeed most ORM frameworks allow low-level access to perform specific operations).
  • SQL is (mostly) a lingua franca for all database systems, whereas different ORM frameworks may be rather different in their instruction set.
  • Finally, ORM may have sub-optimal performance in some cases where the generated SQL is not tailored to the problem.

API Framework

An API is essentially a list of functions which receive parameters and return data in an HTTP-compatible format, usually built with the help of dedicated frameworks. The availability, number and maturity of these libraries depend on the language of choice.

Here we will focus on Python, a popular choice for server development, which offers powerful frameworks such as django, flask, and FastAPI.

  • Django is a very mature and extremely complete solution, with a wide variety of tools to deal with the needs of a server (e.g., builtin database management).
  • FastAPI is more performant, leaner, and explicitly dedicated to writing API, while other specialized libraries should be used to handle the other aspects of the server.
  • Flask is a versatile framework, somewhat between django and FastAPI in regards to specialization.

Ready to go

And that’s it: these are the choices to be made for a simple server application. In the next posts, we will review the design of one such program, component by component, and go through the implementation.


Author: Adriano Angelone

After obtaining his master in Physics at University of Pisa in 2013, he received his Ph. D. in Physics at Strasbourg University in 2017. He worked as a post-doctoral researcher at Strasbourg University, SISSA (Trieste) and Sorbonne University (Paris), before joining eXact-lab as Scientific Software Developer in 2023.

In eXact-lab, he works on the optimization of computational codes, and on the development of data engineering software.