Using Electron with Haskell
Not much literature exist on using
Electron as a GUI tool for Haskell development, so I thought I'd explore the space a little. Being initially a bit clueless on how
Electron would launch the Haskell web server, I was watching the Electron meetup talk by Mike Craig from Wagon HG (they use
Electron with Haskell) and noticed they actually mention it on the slides:
Statically compiled Haskell executable
· Shipped in Electron app bundle
· main.js in Electron spawns the subprocess
· Executable creates a localhost HTTP server
· JS talks to Haskell over AJAX
Importantly the bit “main.js in Electron spawns the subprocess” is the part that was somehow missing in my mental model of how this would be structured.
Riding on this epiphany, I decided to document my exploration, seeing as this is an area that is sorely lacking (GUI programming in Haskell in general). Before we start anything though, let me lay out what the project structure will look like:
NOTE: If you want to grab the whole code from this post, it can be found at codetalkio/Haskell-Electron-app.
Versions and tools used:
- Electron 1.0.1
- Stackage LTS 5.15
- servant 0.4.4.7
Setting up Electron
Electron has a nice quick start guide, which helps you get going fairly, well, quick. For our purposes, the following will set up the initial app we will use throughout.
$ cd Haskell-Electron-app
$ git clone https://github.com/electron/electron-quick-start haskell-app
$ cd haskell-app
$ npm install && npm start
And that’s it really. You’ve now got a basic
Electron app running locally. The
npm start command is what launches the app for you. But! Before doing anything more here, let's take a look at the Haskell side.
Setting up the Haskell webserver
stack is installed, you can set up a new
servant project with,
$ cd Haskell-Electron-app
$ stack new backend servant
$ cd backend
$ stack build
which will download the
servant project template for you (from the stack templates repo) and build it.
To test that it works run
stack exec backend-exe which will start the executable that
stack build produced. You now have a web server running at
127.0.0.1:8080 - try and navigate to 127.0.0.1:8080/users and check it out! :)
For the lack of a better named I have called the application backend, but it could really be anything you fancy.
Contacting Servant/Haskell from Electron
For now, let us proceed with
servant running separately, and later on explore how we can start the
servant server from inside
servant template project has given us the endpoint
127.0.0.1:8080/users from which it serves
JSON, let's set up
Electron to call that and display the results.
By default the chromium developer tools are enabled in
Electron. I suggest you keep them enabled while debugging, since that makes it a lot easier to see if anything went wrong. If you want to disable it, you just need to comment/remove a line in
Short interlude: we’ll be a bit lazy and download jQuery 2.2.3 minified. Put that into
Haskell-Electron-app/haskell-app/resources/jQuery-2.2.3.min.js so we can include it later on and get the nice AJAX functionality it provides.
Back to work in
Haskell-Electron-app/haskell-app, lets change the
index.html page and prepare it for our list of users.
And finally we’ll implement the logic in
We simply request the
JSON data at
$.getJSON(...), and display it if we received the data. If the request failed, we keep retrying until we either get a response or reach the maximum number of attempts (here set to 50 via
The real purpose behind the retry will become apparent later on, when we might need to wait for the server to become available. Normally you will use a status endpoint that you are 100% sure is correct and not failing to check for the availability (inspired by the answer Mike from Wagon HQ gave here).
Launching the Haskell web server from Electron
Now to the interesting part, let’s try to launch the Haskell web server from inside of
First though, let us set the
haskell-app/resources folder as the target for our binary, in the
stack.yaml file, with the
local-bin-path configuration value.
Now let’s compile the executable.
$ cd Haskell-Electron-app/backend
$ stack build --copy-bins
--copy-bins (or alternatively you can just do
stack install) will copy over the binary to
Haskell-Electron-app/haskell-app/resources as we specified (it defaults to
local-bin-path is not set).
After that it is surprisingly easy to launch the executable from within
Electron-well, easy once you already know how. We will change
main.js to spawn a process for the web server upon app initialization (that is, the
Since there are bits and pieces that are added I’ll include the whole
Haskell-Electron-app/haskell-app/main.js file, with most of the comments removed.
Let’s briefly go through what is happening:
- We are using the child_process.spawn command to launch our backend web server
- We imported the
const child_process = require('child_process')
- Defined a variable
let backendServerthat'll let us keep the backend server from being garbage collected
- Added a function
child_process.spawn('./resources/backend-exe')to spawn the process
- Added the
createBackendServerfunction to the
- Close the
backendServerwhen the event
And voila! We now have Electron spawning a process that runs a Haskell web server! :)
Next step would be to package the app up for distribution to see if that affects anything, but I’ll save that for another time (and
Electron already has a page on distribution here).
Originally published at https://codetalk.io on May 11, 2016.