evitaDB - Fast e-commerce database
logo
page-background

Introducing evitaLab Desktop

Our web-based GUI client evitaLab has proven to be an irreplaceable tool when working with evitaDB. The one major thing that was missing in our daily usage was an easy way to connect to different evitaDB servers. Therefore, from the very beginning we wanted a desktop version that could connect to any evitaDB server from a single place.

And now, we are happy to announce the evitaLab Desktop. You can download it for all the major platforms (Windows, macOS, Linux). It has all the favorite features of the core evitaLab as well as a lot of additional features, such as:
  • ✅ manage connections to multiple evitaDB instances
  • ✅ connect to any server version in one app
    • note: server version must be at least 2025.1, older versions of evitaLab are not and will never be backported to work in the driver mode
  • ✅ receive evitaLab core updates as soon as they are released without waiting for server update
  • ✅ each connection stores its own tabs and history, even if each connection points to same server URL
    • this is useful when you are using port forwarding for different server environments (prod, test, local) where local port is the same for each environment
  • ✅ connections styling for better distinction between servers and environments
  • ✅ global notification system - displays toast notifications from desktop app and connections in one place
    • this way you can see notifications from all connections all the time
evitaLab Desktop preview

Motivation

There were several reasons for developing the desktop application.

The obvious one is that it is much more user friendly to have a dedicated desktop application that can handle multiple server connections with proper differentiation and customisation, rather than switching through random tabs of different evitaLab instances in a web browser.
The other reason, the main reason for why we built it right now, is that it solves us a big problem we have been postponing in the evitaLab core. The problem is that the evitaLab web client doesn't support multiple versions of the evitaDB server. It is always developed against the latest version of evitaDB (more precisely, it is developed against the latest version of gRPC/GraphQL/REST APIs). Therefore, we have been trying to invent some kind of middleware between evitaLab GUI and the evitaDB APIs that would bridge different versions of evitaLab to different versions of evitaDB server. More specifically, to allow evitaLab to support older and newer evitaDB servers at the same time.

Initially, we wanted to create a universal data model based on GUI requirements and create converters for all evitaDB API versions. However, it soon became clear that this approach would be impossible to maintain. It would require creating separate stubs for each gRPC API version and connecting them to the evitaLab data model. It would also introduce another challenge: how to cleverly distribute the individual stubs and converters to a client without having the client download the entire codebase at once.

Versioned evitaDB client concept

After some thought, we have abandoned the idea of supporting multiple evitaDB server versions within a single evitaLab version. Instead, we have combined several separate ideas we had in mind and came up with the following idea.

The idea behind evitaLab Desktop

The desktop application is not just a simple evitaLab wrapped in WebView and bundled into a desktop application. That would not solve anything for us.

Instead, the desktop application acts as a kind of manager of separate evitaLab instances. The desktop application, among other things, has its own GUI for managing connections to evitaDB servers. When connected to an evitaDB server, the desktop application resolves the version of the connected to evitaDB server and finds the latest compatible evitaLab version. If a compatible evitaLab is found, the desktop application downloads it (if not already done) from the evitaLab GitHub repository and saves it to the filesystem. Finally, it spawns a new WebView that runs the downloaded evitaLab locally from the filesystem in the driver mode.

This way we can connect to any evitaDB version (from 2025.1 forward) without any complex bridges.

When a user switches between individual connections using the GUI of the desktop application, the desktop application simply switches the visibility of the WebViews running the evitaLab instances.

Connections architecture

Problems it solves and its benefits

This architecture solves several problems at once.

The main problem it solves is the "support of multiple versions" of the evitaDB server. With this architecture we can effectively support all evitaDB versions from a single desktop application with almost no maintenance cost. However, it has one disadvantage compared to supporting different evitaDB versions in evitaLab itself: each evitaLab WebView may look slightly different and offer different features if the connected servers have too large a version gap. However, we think this is acceptable because when a user connects to multiple servers with a web browser, each embedded evitaLab instance may also look different. Also, we could theoretically backport some GUI changes to older evitaLab versions to eliminate the differences.

The architecture of local hosting of evitaLab also offers several advantages. For one, because it is hosted locally and does not depend on an evitaDB server, it survives a non-running evitaDB server, which means that a user can still view their queries, data, etc (although it still has some quirks that we need to fine-tune). Another advantage of running the evitaLab locally is that, we don't have to download the source code from the server every time the desktop application is opened, which means faster load times. The evitaDB server doesn't even have to expose the embedded evitaLab (however, this requires that all users use the desktop application), only its API.

Another advantage of not having to rely on the evitaDB embedded evitaLab instance is that we can ship new versions of evitaLab directly to users without waiting the for evitaDB server to catch up.

The desktop application itself offers even more potential benefits that we can use in the future. For example: we can create features that interconnect individual evitaLab instances, for example: migrating catalogs from one server to another. We are already using this type of capability for toast notifications. Notifications from all evitaLab instances are sent to the desktop application, which displays them all in a single list. This allows the user to view all notifications from any connection or from the desktop application itself, regardless of the connection being explored.

Technologies we used

The main component without which it would hardly be possible is of course the Electron framework in conjunction with the Electron Forge. We also explored other options like Tauri or Neutralino. For example, we really liked the idea of the Rust architecture of the Tauri framework, but no other framework (at the time of the research) provided all the features we needed to make idea work. The main feature we needed was the ability to place separate WebViews on top of and next to each other. The "next to each other" is no problem, but the "on top of each other" proved to be quite a problem for frameworks other than Electron. So we were kind of forced to use Electron. Fortunately, it turned out to be quite a good choice, as it comes with a large community and offers a wide range of useful tools and possibilities.
Within the Electron application, we stuck to the same tech stack we use for the core evitaLab: Vue + Vuetify + Typescript. This gives us a low barrier to switch between both projects and allows us to share common frontend components like GUI styling. Ideally, this way a user won't know the difference between the actual desktop application and an evitaLab driver.
Finally, we use the GitHub CI to test and release the entire application for each platform. The release process without the CI would be too painful, because building the application for a specific platform means that you need to access a machine with that platform and build it on that machine. This would require us to manually build the desktop application on at least 4 different machines (even if they were virtual) and then merge them into a single release. Instead, we used different GitHub CI runners to build the desktop application for each platform in parallel jobs and package all of the built files into a single GitHub release. You can check out the entire CI workflow in our repository.

How it works under the hood

We have already mentioned that it is built using the Electron framework. However, there are many ways to build an Electron application.

First of all, we were heavily inspired by the architecture used by the Mattermost Desktop application, but there are some differences. So how does it how work under the hood?

Architecture

Each Electron application is divided into main, preload and renderer components. The evitaLab Desktop application further divides the renderer component into several so-called mini web applications (each is separate renderer process), which together create the entire GUI.

The main component is a Node.js backend that handles communication with the OS, application windows and so on. In our case the main component is written in Typescript and handles, among other things (application updates, filesystem access, app config, etc.), the orchestration of the actual GUI, which consists of the desktop application's own GUI as well as the evitaLab driver GUIs.

The preload component is responsible for providing custom window.XYZ browser APIs to the renderer components. We provide several custom APIs, for example: notifications API, connection manager API, modal manager API and many more. This way any frontend web application can communicate with the rest of the desktop application (such as modal windows or evitaLab drivers) or the OS itself.

The last are the renderer components, which is where the mini frontend web applications are rendered (just like in a web browser). In evitaLab Desktop, we spawn several renderer instances. The base GUI, each modal window, each evitaLab driver, is a separate renderer process (similar to separate tabs in a web browser).

Electron components architecture
Each evitaLab desktop boots with a single fresh BrowserWindow instance, which is basically a new application window that loads its own frontend web application. More precisely it loads a so-called skeleton web application. The skeleton web application is the basic GUI of the desktop application. It consists mainly of the welcome screen (the first thing you see after a startup) and the main navigation bar (for switching connections). This web application is always there in the background, no matter what you are doing.
As soon as you start clicking around and opening connections and modal windows, things get more complex under the hood. As mentioned before, each modal or evitaLab driver is a separate renderer/mini frontend web application that we cleverly place next to each other or on each other on top of the skeleton web application. For this we use the WebContentsView component, which allows us to place serveral separate renderer instances within the main BrowserWindow instance.
In case of connecting to a server, a new WebContentsView instance is spawned and loaded with an appropriate evitaLab driver web application instance. It is then statically placed over a part of the skeleton application, so that only the left navigation bar is visible. If more connections are opened and thus more WebContentsView instances with evitaLab driver are spawned, they are stacked on top of each other at the same place, but only one instance is set as visible at that time. The others are still there, but hidden.
In the case of modal windows, each renderer can open any existing modal window using the provided modal manager API. Each modal window is also a separate renderer. When a new modal window is requested through the API, the backend spawns a new WebContentView instance and loads the appropriate frontend web application for that modal. Such a WebContentView instance is placed over the entire BrowserWindow, but with a transparent background. This way the rest of the desktop application is still visible underneath, giving the illusion of a unified application. When the modal window is closed, the WebContentView instance is not destroyed, instead the modal data is cleared and the WebContentView instance is hidden. This way, when the modal is requested again, we don't have to initialise everything again, we just toggle the visibility. This approach also allows stacking multiple modal windows on top of each other, for example if a modal window wants to spawn a confirmation modal window. The new modal is simply placed on top of the original modal window.

Architecture drawbacks

Of course, the approach of separate frontend web applications running in separate renderer instances has some drawbacks. The most important one is memory consumption. Each renderer instance spawns its own process with its own miniature browser view, just as a normal browser does with separate tabs. However, in our application we don't necessarily need separate browser contexts for each modal window. Another drawback is that it requires some code boilerplate, specifically each such mini application needs its own index.html that initialises a small Vue.js app, which needs some configuration and plugins (which is the same for most mini apps). And this initialisation takes some time. On today's computers, the initialisation time seems negligible, but it is still there, and it kind of wastes computer resources. However, the benefit of running multiple instances of evitaLab in a single desktop application with an almost seamless experience without much work, in our opinion, far outweighs the drawbacks. Of course we plan to optimise the architecture where possible, but even now it seems work quite well.

Now that we know how the desktop application works, let's dive into some specific problems we needed to solve.

evitaLab driver version resolution

Once we decided to drop the idea of supporting multiple servers within a core evitaLab, we were faced with another problem: how to decide which evitaLab driver version to choose for a specific server?

We researched several ways, but finally settled on the following.

As mentioned in the beginning, each evitaLab driver is developed against the latest version of evitaDB APIs, not against the evitaDB server version itself. This means that we can define the required evitaDB API version for a specific evitaLab version. In reality, we have introduced an .evitadbrc file in the root of the evitaLab project, which specifies the minimum required version of the evitaDB server for a specific evitaLab version. Then, when a new evitaLab version is released, the GitHub CI pushes the minimum evitaDB version with the released evitaLab version to our public metadata database by simply pulling the database repository, adding the version pair to the index file and committing it (check out the release.yaml action).
evitalab-versionmin-evitadb-version
2025.2.02025.1.0
2025.3.02025.1.0
evitalab-database.csv

Then, when a user tries to connect to a specific evitaDB server from the desktop application, the application downloads the current index file from the metadata database, creates a sorted index grouped by the minimum evitaDB version, and searches for the newest evitaDB version that is older than or equal to the connected server. Once found, it selects the latest known evitaLab driver version for that evitaDB version.

Since we use the semantic calendar versioning for both the evitaDB and the evitaLab projects, which is basically a variation of the semantic versioning, we can easily compare the versions and choose the latest one. This also means, that we can backport some features to older evitaLab versions that require older evitaDB versions. And by incrementing only the PATCH part of the version, the desktop application would update the driver only to the newer patch version without breaking any compatibility that would occur by upgrading to a newer driver version that requires a newer evitaDB version.

evitaLab driver

In order to support running the evitaLab core embedded in the evitaDB server as well as in the desktop application, we now build the evitaLab in two modes: standalone and driver mode using the GitHub CI. Each of them has some small differences to fit its target area.

The driver mode typically changes some GUI parts to make the transition from the desktop application to the evitaLab driver seamless. It also integrates evitaLab with the custom APIs provided by the preload script.

After resolving a specific driver version, we first check if the driver source code is already present on the filesystem. If not, a temporary directory is created and an HTTP client is used to download the driver mode build archive from the following source URL

into this temporary directory. The downloaded archive is then unzipped. Finally, the temporary directory is renamed to the driver version. We use the temporary directory to prevent any source code corruption during this process. For example, a scenario where the desktop application crashes in the middle of the download or during decompression. If we used the driver version named directory directly and such corruption occurred, the desktop application would still think that the driver was correctly downloaded and usable, which would lead to a runtime crash. Of course, proper checksum verification would be even better, but for now this will do.

When a new WebContentsView instance is spawned, the evitaLab driver is loaded directly from the filesystem from the driver directory.

Autoupdates

The Electron framework comes with the update-electron-app library which is able to auto update the desktop application from GitHub Releases. Since we use Electron Forge and GitHub Release to publish released versions, it was no brainer to choose this library. The only thing needed was some simple configuration:
However, there was one small obstacle. Although we are using Electron Forge, we are using the Vite + Typescript template for the project structure. The problem is that the update-electron-app library expects a package.json file in the parent directory of the running application. However, Vite places it in the parent of parent directory, effectively crashing the entire update process. We ended up temporarily cloning the library directly into the desktop application until the library is fixed ( we are working on the fix).

Unfortunately, the auto-update feature is only available for Windows and macOS platforms. Linux is not supported at all. Therefore, for the Linux version, we have implemented a notification system in the application that notifies the user about a new version and redirects them to the GitHub Releases for manual installation.

Conclusion

So far, the Electron framework has proven to be a great tool and has allowed us to build an application that would be difficult to build with other frameworks for all major platforms.

Feel free to download the evitaLab Desktop application and try it out. If you find any problems, please report them to our repository. If you want to explore the codebase and tinker with it, feel free to clone the repository and run the development version.