How to create a desktop application with Electron [Tutorial]

Electron is an open source framework, created by GitHub, that lets you develop desktop executables that bring together Node and Chrome to provide a full GUI experience. Electron has been used for several well-known projects, including developer tools such as Visual Studio Code, Atom, and Light Table.

Basically, you can define the UI with HTML, CSS, and JS (or using React, as we’ll be doing), but you can also use all of the packages and functions in Node. So, you won’t be limited to a sandboxed experience, being able to go beyond what you could do with just a browser.

Wait until it’s loaded, and then and only then, move on to the next step.

Start Electron.

So, basically, you’ll have to run the following two commands, but you’ll need to do so in separate terminals:

// in the directory for our restful server:
node out/restful_server_cors.js

// in the React app directory:
npm start

// and after the React app is running, in other terminal:npm run electron

After starting Electron, a screen quickly comes up, and we again find our countries and regions app, now running independently of a browser:

The app works as always; as an example, I selected a country, Canada, and correctly got its list of regions:

We are done! You can see that everything is interconnected, as before, in the sense that if you make any changes to the React source code, they will be instantly reflected in the Electron app.

Adding Node functionality to your app

In the previous recipe, we saw that with just a few small configuration changes, we can turn our web page into an application. However, you’re still restricted in terms of what you can do, because you are still using only those features available in a sandboxed browser window. You don’t have to think this way, for you can add basically all Node functionality using functions that let you go beyond the limits of the web. Let’s see how to do it in this recipe.

How to do it

We want to add some functionality to our app of the kind that a typical desktop would have. The key to adding Node functions to your app is to use the remote module in Electron. With it, your browser code can invoke methods of the main process, and thus gain access to extra functionality.

Let’s say we wanted to add the possibility of saving the list of a country’s regions to a file. We’d require access to the fs module to be able to write a file, and we’d also need to open a dialog box to select what file to write to. In our serviceApi.js file, we would add the following functions:

When the saveRegionsToDisk() action is dispatched, it will show a dialog to prompt the user to select what file is to be written, and will then write the current set of regions, taken from getState().regions, to the selected file in JSON format. We just have to add the appropriate button to our component to be able to dispatch the necessary action:

How it works

The code we added showed how we could gain access to a Node package (fs, in our case) and some extra functions, such as showing a Save to disk dialog. When we run our updated app and select a country, we’ll see our newly added button, as in the following screenshot:

Clicking on the button will pop up a dialog, allowing you to select the destination for the data:

If you click Save, the list of regions will be written in JSON format, as we specified earlier in our writeRegionsToDisk() function.

Building a more windowy experience

In the previous recipe, we added the possibility of using any and all of the functions provided by Node. In this recipe, let’s now focus on making our app more window-like, with icons, menus, and so on. We want the user to really believe that they’re using a native app, with all the features that they would be accustomed to.

The following list of interesting subjects from Electron APIs is just a short list of highlights, but there are many more available options:

clipboardTo do copy and paste operations using the system’s clipboarddialogTo show the native system dialogs for messages, alerts, opening and saving files, and so onglobalShortcutTo detect keyboard shortcutsMenu, MenuItemTo create a menu bar with menus and submenusNotificationTo add desktop notificationspowerMonitor, powerSaveBlockerTo monitor power state changes, and to disable entering sleep modescreenTo get information about the screen, displays, and so onTrayTo add icons and context menus to the system’s tray

Let’s add a few of these functions so that we can get a better-looking app that is more integrated to the desktop.

How to do it

Any decent app should probably have at least an icon and a menu, possibly with some keyboard shortcuts, so let’s add those features now, and just for the sake of it, let’s also add some notifications for when regions are written to disk. Together with the Save dialog we already used, this means that our app will include several native windowing features.

To start with, let’s add an icon. Showing an icon is the simplest thing because it just requires an extra option when creating the BrowserWindow() object. I’m not very graphics-visual-designer oriented, so I just downloaded the Alphabet, letter, r Icon Free file from the Icon-Icons website. Implement the icon as follows:

You can also choose icons for the system tray, although there’s no way of using our regions app in that context, but you may want to look into it nonetheless.

To continue, the second feature we’ll add is a menu, with some global shortcuts to boot. In our App.regions.js file, we’ll need to add a few lines to access the Menu module, and to define our menu itself:

Using a template is a simple way to create a menu, but you can also do it manually, adding item by item. I decided to have a Countries menu with two options to show the regions for Uruguay and Hungary. The click property dispatches the appropriate action. I also used the accelerator property to define global shortcuts. See the accelerator.md for the list of possible key combinations to use, including the following:

Command keys, such as Command (or Cmd), Control (or Ctrl), or both (CommandOrControl or CmdOrCtrl)

Alternate keys, such as Alt, AltGr, or Option

Common keys, such as Shift, Escape (or Esc), Tab, Backspace, Insert, or Delete

I also want to be able to quit the application. A complete list of roles is available at Electron docs. With these roles, you can do a huge amount, including some specific macOS functions, along with the following:

Work with the clipboard (cut, copy, paste, and pasteAndMatchStyle)

Handle the window (minimize, close, quit, reload, and forceReload)

Zoom (zoomIn, zoomOut, and resetZoom)

To finish, and really just for the sake of it, let’s add a notification trigger for when a file is written. Electron has a Notification module, but I opted to use node-notifier, which is quite simple to use. First, we’ll add the package in the usual fashion:

npm install node-notifier --save

In serviceApi.js, we’ll have to export the new function, so we’ll able to import from elsewhere, as we’ll see shortly:

const electron = window.require("electron").remote;

.
.
.

export const notifier = electron.require("node-notifier");

Finally, let’s use this in our world.actions.js file:

import {
notifier,
.
.
.
} from "./serviceApi";

With all our setup, actually sending a notification is quite simple, requiring very little code:

How it works

Now, let’s look at the menu. It has our options, including the shortcuts:

Then, if we select an option with either the mouse or the global shortcut, the screen correctly loads the expected regions:

Finally, let’s see if the notifications work as expected. If we click on the Save regions to disk button and select a file, we’ll see a notification, as in the following screenshot:

Making a distributable package

Now that we have a full app, all that’s left to do is package it up so that you can deliver it as an executable file for Windows, Linux, or macOS users.

How to do it.

There are many ways of packaging an app, but we’ll use a tool, electron-builder, that will make it even easier, if you can get its configuration right!

First of all, we’ll have to begin by defining the build configuration, and our initial step will be, as always, to install the tool:

npm install electron-builder --save-dev

To access the added tool, we’ll require a new script, which we’ll add in package.json:

"scripts": {
"dist": "electron-builder",
.
.
.
}

We’ll also have to add a few more details to package.json, which are needed for the build process and the produced app. In particular, the homepage change is required, because the CRA-created index.html file uses absolute paths that won’t work later with Electron:

Finally, some specific building configuration will be required. You cannot build for macOS with a Linux or Windows machine, so I’ll leave that configuration out. We have to specify where the files will be found, what compression method to use, and so on:

We have completed the required configuration, but there are also some changes to do in the code itself, and we’ll have to adapt the code for building the package. When the packaged app runs, there won’t be any webpack server running; the code will be taken from the built React package. The starter code will require the following changes:

Mainly, we are taking icons and code from the build/ directory. An npm run build command will take care of generating that directory, so we can proceed with creating our executable app.

How it works

After doing this setup, building the app is essentially trivial. Just do the following, and all the distributable files will be found in the dist/ directory:

npm run electron-builder

Now that we have the Linux app, we can run it by unzipping the .zip file and clicking on the chapter13 executable. (The name came from the "name" attribute in package.json, which we modified earlier.) The result should be like what’s shown in the following screenshot:

I also wanted to try out the Windows EXE file. Since I didn’t have a Windows machine, I made do by downloading a free VirtualBox virtual machine.

After downloading the virtual machine, setting it up in VirtualBox, and finally running it, the result that was produced was the same as for Linux:

So, we’ve managed to develop a React app, enhanced it with the Node and Electron features, and finally packaged it for different operating systems. With that, we are done!

If you found this post useful, do check out the book, Modern JavaScript Web Development Cookbook. You will learn how to create native mobile applications for Android and iOS with React Native, build client-side web applications using React and Redux, and much more.