Desktop apps are often a mix of UI and business logic.
In some cases, the business logic runs in a server and the desktop app may be a simple UI that interacts with said server executing CRUD operations.
In other cases, the app itself needs to contain the business logic and perform expensive computations (ex. machine learning, CPU intensive calculations).
Electron.js is a powerful framework that enables JS developers to create cross-platform desktop apps with the same technology stack used in the web. While the default process model of Electron scales well for UI heavy apps, there are limitations that may cause laggy UIs and poor developer experience.
This starter aims to bridge those limitations, focusing on performance and developer experience. It combines the latest web libraries with Electron.
This is exactly how my app Taggr is architected.
As mentioned above, this starter proposes an advanced architecture, best suited for complex Electron.js apps. Avoid over engineering your solution in the beginning, and consider the switch if you experience performance or developer experience limitations.
The architecture of this starter is divided into three modules: frontend
, electron-backend
and shared
.
The code sharing between modules (the shared
module) is managed with yarn workspaces.
Stack: TypeScript + React (CRA) + Redux + Storybook
The frontend module is responsible of all things UI, no business logic should live in this module.
During development, the frontend is served live from the webpack dev-server (from http://localhost:3000). In production, the frontend statics are build and copied into the installable.
- Full TypeScript support.
- Develop components in isolation using Storybook.
- Store the fully-typed app state in Redux.
- Interact with the electron-backend through async message passing with
node-ipc
. - Access to the
shared
module (through yarn workspaces). - Tests with jest.
Stack: TypeScript + Node.js
The electron-backend module is responsible for the business logic, and the integrations with external dependencies such as databases and REST APIs.
It also contains the electron code required to build and package the app into a cross-platform executable.
In development, the backend code runs withing an Electron render process (browser window). In production, the backend code runs as a separate node.js process (forked from the electron node.js process).
The architecture schema when in development:
- Full TypeScript support.
- Perform costly CPU and GPU operations without impacting the UI.
- Support for native modules.
- Interact with the frontend through async message passing with
node-ipc
. - Access to the
shared
module (through yarn workspaces). - Tests with jest.
Stack: TypeScript
There are great benefits to having a decoupled architecture. It is often easier to reason about and modify parts of it without impacting the other parts of the system, as long as the interaction contract between the modules it kept (API).
Since the frontend
and electron-backend
are separated modules, shared
enables code sharing between them. This allows to define the frontend and electron-backend async event passing APIs in one place, and evolve the modules separatedly.
- Full TypeScript support
- Enable code sharing between
fronted
andelectron-backend
(through yarn workspaces).
The communication between the frontend
and electron-backend
modules is done by node-ipc, following the blueprint proposed in this great post.
It uses asynchronous message passing to transfer the events, and the communication layer is typed in the shared
module. By doing so, both the frontend
and electron-backend
are decoupled, and the communication layer offers full type safety.
The frontend
can trigger code in the backend
by passing messages, and wait for an answer asynchronously. However, in some cases, the operations that backend
should do may take significant time and resources.
Thats why the frontend
also exposes event handlers, so that the backend
can modify the state of the frontend
(redux store) when needed.
# install dependencies
yarn
# watch changes in the modules
yarn modules:watch
# start the app in development mode
yarn watch
In order to refresh the Backend or Frontend window, you can press command
+ R
.
yarn build
# executable in: `./electron-backend/dist`