Communication Between Frontend and Backend in Electron
So, you’ve built a basic Electron app that looks pretty slick. But, if you want to move beyond static apps, it’s time to dive into how Electron handles communication between the frontend (renderer process) and backend (main process). Don’t worry if that sounds complicated. Think of it like this: the renderer handles what the user sees (HTML, CSS, JavaScript), and the main process is working behind the scenes to keep things running smoothly.
In this tutorial, we’re going to cover IPC (Inter-Process Communication), which is a fancy way of saying “talking between the frontend and backend.” By the end of this, you’ll be able to build dynamic apps that pass information back and forth between the UI and the system logic. Ready? Let’s go!
What You’ll Learn:
- What the main and renderer processes are in Electron.
- How to use Electron’s IPC module for communication between these processes.
- Setting up listeners and senders to handle two-way communication.
Understanding Main vs. Renderer Processes
Before we start coding, let’s get the basics down.
-
Main Process: This is the backend of your app. It’s responsible for managing system events, creating windows, and handling heavy tasks like accessing the file system or interacting with APIs. There’s only one main process per Electron app.
-
Renderer Process: This is the frontend—everything the user sees and interacts with. Each window in your Electron app gets its own renderer process.
Think of it like a tag team. The main process is the backstage crew, handling all the heavy lifting, while the renderer process is the actor on stage, interacting with the audience.
Why Can’t the Renderer Do Everything?
Well, Electron is built with security in mind. The renderer process has limited access to the system for safety reasons, while the main process has full control. But don’t worry, using IPC, these two can talk and exchange data!
Step 1: Setting Up Your App for IPC
We’ll start by revisiting our project setup from previous lessons. If you’ve followed along, your project should look something like this:
my-electron-app/
├── assets/
│ ├── script.js
│ └── styles.css
├── main.js
├── index.html
├── package.json
└── node_modules/
We’ll add some basic functionality to demonstrate how the main process and renderer process communicate. Let’s start by editing the main.js and script.js files.
Step 2: Implementing IPC in Main Process
First, let’s set up the main process. Open main.js, and modify it to include IPC communication.
// main.js
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');
function createWindow() {
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: false,
contextIsolation: true,
enableRemoteModule: false
}
});
mainWindow.loadFile('index.html');
}
// Listen for an IPC message from the renderer
ipcMain.on('message-from-renderer', (event, message) => {
console.log('Message from Renderer:', message);
// Send a reply back to the renderer
event.reply('message-from-main', 'Hello from the Main Process!');
});
app.whenReady().then(createWindow);
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
What’s Happening Here?
-
ipcMain: We require ipcMain from Electron, which allows the main process to listen for messages from the renderer process.
-
ipcMain.on(): This method listens for the message called
'message-from-renderer'
. When it receives this message, it logs the message sent by the renderer and sends a reply back usingevent.reply()
. -
event.reply(): This sends a message back to the renderer process, which we’ll set up next.
Step 3: Adding IPC in Renderer Process
Next, let’s set up the renderer process to send a message to the main process and listen for a reply. Open the script.js file and modify it:
// script.js
const { ipcRenderer } = require('electron');
// Send a message to the main process
document.getElementById('magicBtn').addEventListener('click', () => {
ipcRenderer.send('message-from-renderer', 'Hello from Renderer!');
});
// Listen for a reply from the main process
ipcRenderer.on('message-from-main', (event, message) => {
const messageParagraph = document.getElementById('message');
messageParagraph.textContent = message;
});
What’s Happening Here?
-
ipcRenderer: This is the counterpart to ipcMain, but it lives in the renderer process. It lets the frontend send messages to the main process and listen for responses.
-
ipcRenderer.send(): When the button is clicked, this sends the message
'message-from-renderer'
along with some text ('Hello from Renderer!'
) to the main process. -
ipcRenderer.on(): This listens for a reply from the main process (specifically for the message
'message-from-main'
) and updates the paragraph with the message received.
Step 4: Setting Up Preload Script for Security
Electron apps use a preload script to securely expose APIs like ipcRenderer to the renderer process. Without this, the renderer wouldn’t have access to ipcRenderer directly.
Here’s how to set up the preload.js file:
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld(
'electronAPI', {
sendToMain: (channel, data) => ipcRenderer.send(channel, data),
onMainReply: (channel, func) => ipcRenderer.on(channel, (event, ...args) => func(...args))
}
);
What’s Happening Here?
-
contextBridge: This is used to securely expose a limited API to the renderer process, which avoids giving it direct access to Node.js features.
-
electronAPI: We expose an API where the renderer can use
sendToMain()
to send messages andonMainReply()
to listen for replies.
Now, in script.js, you’d modify the communication like this:
// script.js
document.getElementById('magicBtn').addEventListener('click', () => {
window.electronAPI.sendToMain('message-from-renderer', 'Hello from Renderer!');
});
window.electronAPI.onMainReply('message-from-main', (message) => {
document.getElementById('message').textContent = message;
});
Step 5: Testing Your App
Once you've added the IPC code, it’s time to test your app! Here’s how to do it:
- Open your terminal.
- Navigate to your project directory.
- Run the app using
npx electron .
.
Click the button, and check out your console (if you have dev tools open) or your terminal. You should see the message 'Hello from Renderer!'
logged in the console. Then, the text "Hello from the Main Process!" should appear in the app’s UI.
Step 6: Two-Way Communication in Action
This example is basic, but IPC opens up a world of possibilities. You can use this to:
- Send user data from the frontend to the backend, like form inputs or settings.
- Get system-level data in the backend and send it to the frontend, like accessing the file system, reading files, or interacting with external APIs.
- Real-time updates: If your app needs real-time updates (like chat apps or stock tickers), you can keep communication flowing between the main and renderer processes using IPC.
Step 7: Extending IPC
Here are some ideas to extend your app’s functionality:
-
File Access: Use IPC to let the user select a file from their system (from the renderer) and pass it to the main process to read the file.
-
Data Fetching: Fetch data from an external API in the main process and send it to the renderer to display to the user.
-
Background Tasks: Perform background tasks (like downloading files) in the main process and update the renderer with progress info using IPC.
Conclusion
You did it! You now know how to set up communication between the main and renderer processes using Electron’s IPC module. This two-way communication is the backbone of many real-world Electron apps, allowing the frontend to stay responsive while the backend does the heavy lifting.
what we covered:
- The difference between the main and renderer processes.
- How to use ipcMain and ipcRenderer to send messages between these processes.
- Setting up a secure preload script to safely expose the IPC API to your renderer.
In this tutorial, we explored the main and renderer processes in Electron and demonstrated how to set up IPC (Inter-Process Communication) between them. We walked through the setup of the main process using ipcMain to receive messages from the renderer process and respond using event.reply(). We also created the renderer process code to send messages using ipcRenderer and listen for replies.
We also discussed securing the renderer process with a preload script using contextBridge to expose safe APIs to the renderer. By the end of this tutorial, we built a basic two-way communication system between the frontend and backend of your Electron app.
You now have the tools to create more interactive, dynamic apps where the backend can perform tasks like accessing the file system, handling background processes, or fetching data, while the frontend remains responsive to user actions.
In the next lesson, we’ll take this a step further by learning how to manage multiple windows and handle communication between them. Get ready to open new doors (literally) in your app development!