Electron JS + React JS + Express JS

Keshav Agrawal
8 min readJun 10, 2019

--

Requirement: Build a simple cross platform desktop app

Note: I m not a core frontend developer but I keep building many apps as pass time: Web Apps, Desktop Apps etc. So excuse any general conventions not being followed. Intention is to put up a gist on how to connect these 3 dots: Electron JS, React JS and Express JS!

Requirement in detail:

  1. App should not require any installation.
  2. App should be executable directly from a flash drive!
  3. App should run on any platform: mac OSX, Linux and Windows.

What did I try?

Well QT Framework is well known in this domain so I quickly jumped on PyQT- pyqt5 to be specific. I like python so pyqt was a go-to framework for me. — plus I already had some experience with it. Combine it with fbs and you get a bundler which generates platform specific executable — ‘.app’ for MacOSX, ‘.exe’ for windows etc.

But it comes with its own learning curve — biggest one being designing the Fronend — you can’t have a single css file as such. There are provisions to write palette file in PyQT but trust me you will start seeing its limitations as you develop your app. I don’t intend to dive deep into QT Framework here but have to build some context:

So PyQT helped you with Frontend — but what about backend?

  • Well since you can directly call python functions capturing any QT Frontend event you would think I don’t need any backend. Eg: on this button click modify the text of X textbox — possible with just python!
  • So what exactly is the issue? — designing every single thing on UI with just QT and linking each and everything with python methods is not an easy task.
  • Thoughts turned to render a browser inside PyQT engine using chrome web engine. If that can be done then I would be able to handle frontend with js/html.
  • Great! Started working on PyQT Web Engine — lot of hiccups but worked. Now I needed a backend server running to which frontend will make request.
  • Started working on integrating Flask/Django app with PyQT — need to use multithread to launch PyQT process on one hand and then Flask/Django server on the other. This both initiation should happen at one single click of App Start!
  • Had almost integrated the above combination but then something happened- Electron JS enters the stage!

So Electron JS similar to PyQT allows me to achieve Write Once Use Anywhere cross platform apps concept!

Lets jump into the code(from the Electron JS Homepage):

# Clone the Quick Start repository
$ git clone https://github.com/electron/electron-quick-start
# Go into the repository
$ cd electron-quick-start
# Install the dependencies and run
$ npm install && npm start

Bam! And you have it -a simple desktop app:

The Famous Hello World Example

This is good but not exactly what I needed. This I could have easily acheived from PyQT as well. I need a backend nodeJS server and once I start the app I should be able to launch both nodeJS server for backend handling and Electron GUI for handling user interactions -similar to what I was doing with QT and Flask/Django.

  1. I thought: “Lets install Express JS to make a simple REST API backend”
npm install express

2. Okay now how do I plug Electron and Express together?

Lets create a simple app.js which will act as a server for us:

(
function() {
"use strict";
let express = require('express');
let app = express();
app.get('/', function(req, res) {
res.send("Hello world! Lala Seth is here!");
});
let server = app.listen(3000, function () {
console.log('Express server listening on port ' + server.address().port);
});
module.exports = app; }()
);

Now lets have a look at our main.js file which must have been created post quickstart elelctron app installation.

// Modules to control application life and create native browser window
const {app, BrowserWindow} = require('electron');
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow;
function createWindow () {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
})
// and load the index.html of the app.
mainWindow.loadFile('index.html');
// Open the DevTools.
// mainWindow.webContents.openDevTools()
// Emitted when the window is closed.
mainWindow.on('closed', function () {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null
})
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)
// Quit when all windows are closed.
app.on('window-all-closed', function () {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') app.quit()
})
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) createWindow()
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

Remember how Thor was holding things together while Eitri was building Stormbreaker on planet Nidavellir? — Avengers fans would know!

Well we don’t have to deal with the wrath of a Neutron star here. We have a simple way:

If you notice we are loading index.html in our main.js file which is called( via `electron .`) when you do:

npm start

look at its package.json file for more clarity.

Now at the end of index.html you will notice:

<script>
require('./renderer.js');
</script>

Okay! So this renderer.js is being called. Great! I opened renderer.js and wrote this one single line:

let server = require('./app');

So now when my renderer.js is loaded through index.html it will spawn my express server on 3000 port and simultaneously my GUI is ready and rendered for the user!

Now I restart my app with npm start command and I can see the same Hello World screen but now a node server is up at the backend to receive request!

How do you check if the express server is up? Just fire up your favorite browser and visit http://localhost:3000/ if you see “Hello world! Lala Seth is here!” -you are good!

Okay so front end ready, backend ready -all in a single desktop app which can be run directly on any platform!

But developer’s greed is infectious! I need bootstrap for better UI management. I need React JS for reuse of components or say -its the new hot thing out there ;)

To keep things simple I just downloaded a free theme’s css and added it in index.html. Installed bootstrap:

npm install bootstrap

Now I can beautify those labels, headers, arrange DOM elements around Grids and what not! All you need is to add the following under the head tag of my index.html:

<script>
// You can also require other files to run in this process
require('bootstrap');
</script>
<link href="static/css/myfreetheme.min.css" rel="stylesheet"/>
<link href="static/css/app.css" rel="stylesheet"/>

Designed a simple side menu:

But now I need React JS to handle my click events and render my profile Component and records component respectively as per my side menu.

So how do I plug React JS and Electron and Express? I would like to write JSX as well.

So I would need babel and react. And babel-preset-react to let babel know we would be writing JSX!

npm install babel babel-cli babel-core babel-preset-react react react-dom react-router-dom

Folks who have worked on React JS why would they need react-dom and react-router-dom. I would refrain from explaining their usage here.

Lets write some react code now!
1. Create a component folder
2. Add a hello.js file in that directory.
3. Add a <div> with id body in index.html.
4. Put the following as the content of hello.js:

let react = require("react");
let React = require("react");
let reactDOM = require("react-dom");
let reactRouter = require("react-router-dom");
let Component = react.Component;
let BrowserRouter = reactRouter.BrowserRouter;
let Switch = reactRouter.Switch;
let Route = reactRouter.Route;
class Hello extends react.Component {
render() {
return (
<div className="col-xs-12">
Hello World!
</div>
);
}
}
function BaseApp() {
return (
<Switch>
<Route path="/" component={Hello}/>
</Switch>
)
}
class App extends Component {
render() {
return (
<BaseApp/>
);
}
}
reactDOM.render(
<BrowserRouter>
<App/>
</BrowserRouter>,
document.getElementById("body")
);

Wait! You would also need to setup babel config else it would raise Syntax Error while compiling JSX code in our hello.js. Lets head to our package.json and add the following post our devDependencies section:

"babel": {
"presets": [
"react"
]
},

Now lets connect the dots: react, JSX and electron!

  1. Lets create a file: main.boot.js. Content of main.boot.js:
require('babel-register');
require('./main.js');

2. Open package.json and replace:

"main": "main.js",

with

"main": "main.boot.js",

3. Inside our renderer.js lets add some code(to load our react component):

require('./components/hello.js');

Our final renderer.js looks like:

let server = require('./app');
require('./components/hello.js');

Gist: To make entire thing simple: when you do npm start here is how the things flow =>

npm start => electron . => triggers main.boot.js => install babel hooks in the main process and load main.js => shows app window and to render html it calls index.html => this calls renderer.js => which calls our react component as well as spawns express server parallely and final UI gets loaded!

Now I have a boilerplate code with all the 3 components integrated: Express JS for REST API, Electron JS for GUI for desktop app and React JS for -you know- again UI!

One last step | Generating executable

While there are many modules the one I like the most is: https://github.com/electron-userland/electron-packager

Show me the code:

electron-packager . --all

--all will generate executable for all the platforms — mac, windows, linux. There are many options which you can checkout on electron-packager website!

The above command generated a myapp.app, myapp.exe(for both 32 and 64 bit platform) and linux executable. To quickly test things
- I copied the Win32 exe file along with its helper files on a USB Flash drive
- Connected the drive to a windows machine
- Opened the folder and ran the exe file directly from the Flash drive folder
- Voila! I see the same “Hello world” window which I saw on my Mac!

We can do all kinds of optimization and improve things before taking things live on production!

But I hope this blog helps folks out there to have a quick start on Cross Platform Apps!

Request to readers: Please share your feedbacks.
Note: Its not a production ready code but to showcase how we can connect three component to make a cross platform app!

--

--

Keshav Agrawal

Human | Developer | I feel everyone out there has some conscience — it’s about the level of it which defines how it will impact others around you!