DEV Community

Tomasz Wegrzanowski
Tomasz Wegrzanowski

Posted on • Updated on

Electron Adventures: Episode 18: Sending Data To Backend

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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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()
})
Enter fullscreen mode Exit fullscreen mode

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)
})
Enter fullscreen mode Exit fullscreen mode

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)
})
Enter fullscreen mode Exit fullscreen mode

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}`
})
Enter fullscreen mode Exit fullscreen mode

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:

Episode 18 Screenshot

As usual, all the code for the episode is here.

Discussion (0)