Electron apps have frontend process (called "renderer") and backend process (called "main"). There's also small bridging code in between ("preload") that has access to backend API on the frontend.
So far we completely ignored the backend process, and did everything in the frontend and the preload.
Let's see how backend and frontend can communicate. We'll turn off all security for now, so we can see the relevat parts more clearly. We'll get to how to do it more securily later.
Starting a new app
Let's do something about that. Starting a new no-framework:
$ npm init -y
$ npm install --save-dev electron
index.html
Let's start with a simple index.html
. Other than some styling, it's just a single line form, plus some div for printing data.
<!DOCTYPE html>
<html>
<body>
<style>
body {
background-color: #444;
color: #ccc;
font-family: monospace;
font-size: 24px;
}
form {
display: flex;
}
input {
background-color: inherit;
color: inherit;
font-family: inherit;
border: none;
font-size: inherit;
flex: 1;
}
</style>
<h1>Print to terminal</h1>
<form>
<input type="text" autofocus />
</form>
<div id="responses"></div>
<script src="app.js"></script>
</body>
</html>
backend code index.js
We can start it just like before. We'll add one extra thing to this file, later, but for now, let's just open index.html
and give it full privileges:
let { app, BrowserWindow } = require("electron")
function createWindow() {
let win = new BrowserWindow({
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
}
})
win.loadFile("index.html")
}
app.on("ready", createWindow)
app.on("window-all-closed", () => {
app.quit()
})
frontend code app.js
In frontend we need an event handler for when user submits data. So we grab a few DOM elements, and then get data submitted, send it to backend, get its response, and append it to the #responses
div.
let form = document.querySelector("form")
let input = document.querySelector("input")
let responses = document.querySelector("#responses")
form.addEventListener("submit", async (e) => {
e.preventDefault()
let line = input.value
input.value = ""
let responseText = // what do we do here?
let response = document.createElement("div")
response.textContent = responseText
responses.appendChild(response)
})
How can we send data to the backend? Here's how:
let { ipcRenderer } = require("electron")
let form = document.querySelector("form")
let input = document.querySelector("input")
let responses = document.querySelector("#responses")
form.addEventListener("submit", async (e) => {
e.preventDefault()
let line = input.value
input.value = ""
let responseText = await ipcRenderer.invoke("console", line)
let response = document.createElement("div")
response.textContent = responseText
responses.appendChild(response)
})
IPC is "inter-process communication", or a way for different processes to communicate. It sort of looks like we're calling a function (which I called console
- but that's completely arbitrary) in the main process.
Behind the scenes arguments get serialized (sort of like turned into JSON string), promise returned, and then once we get response, the response gets deserialized (sort of like turnes from JSON string back into normal objects), and promise resolves to whatever backend returned.
Backend handler
Backend has ipcMain
corresponding to ipcRenderer
. invoke
corresponds to handle
.
let { ipcMain } = require("electron")
ipcMain.handle("console", (event, line) => {
console.log(`Received from frontend: ${line}`)
return `Backend confirms it received: ${line}`
})
As you can guess, it works similar both ways, if you wanted to send the messages from backendh to frondent you'd do ipcMain.invoke
and ipcRenderer.handle
.
There are also a few other ways to communicate than just invoke
+handle
, and we'll get some uses for them eventually.
Result
And here's the result:
As usual, all the code for the episode is here.
Top comments (0)