What Electron Is
Electron packages Chromium with Node.js into a single runtime. You write your app in HTML, CSS, and JavaScript and ship it as a native desktop executable for Windows, macOS, and Linux. VS Code, Slack, Figma's desktop client — all Electron.
The Three Core Files
my-electron-app/
├── index.html ← the UI rendered in the app window
├── main.js ← Node.js main process — window management, app lifecycle
└── preload.js ← bridge — safely exposes Node APIs to the renderer
Enter fullscreen mode Exit fullscreen mode
Quick setup:
mkdir my-electron-app && cd my-electron-app
npm init -y
npm install --save-dev electron
Enter fullscreen mode Exit fullscreen mode
Add to package.json:
{ "scripts": { "start": "electron ." } }
Enter fullscreen mode Exit fullscreen mode
Minimal main.js:
const { app, BrowserWindow } = require('electron');
const path = require('path');
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
},
});
win.loadFile('index.html');
}
app.whenReady().then(() => {
createWindow();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});
Enter fullscreen mode Exit fullscreen mode
Run npm start — window opens, app is running. That's the foundation everything else builds on.
Main vs. Renderer Process
Main process — one per app, runs in Node.js. Creates windows, handles OS APIs (file system, tray, notifications), manages app lifecycle.
Renderer process — one per window, runs in Chromium. This is where your HTML and frontend JS execute. Sandboxed from Node.js by default.
IPC — how they talk to each other. ipcMain and ipcRenderer pass messages between processes. You'll need this for wiring the update UI.
// main.js — listen for renderer messages
const { ipcMain } = require('electron');
ipcMain.on('check-for-updates', () => autoUpdater.checkForUpdates());
// renderer.js — send message to main
const { ipcRenderer } = require('electron');
document.getElementById('update-btn').addEventListener('click', () => {
ipcRenderer.send('check-for-updates');
});
Enter fullscreen mode Exit fullscreen mode
Packaging
Before distribution, the app needs to be packaged into platform installers. Two tools dominate:
- electron-forge — officially recommended, simpler setup, handles build + package + publish in one workflow
- electron-builder — more configurable, better for complex signing/distribution requirements
For new projects, start with electron-forge unless you have a specific reason not to.
Code Signing
Both macOS and Windows require code signing. Skip it and users get security warnings — on macOS the app can be blocked outright.
macOS — sign at packaging time with an Apple Developer certificate. Notarization (Apple's security scan) is also required since Catalina.
Windows — the distributable installer is signed. Get a certificate from DigiCert, Sectigo, or similar and configure it in your build settings.
Both tools read signing credentials from environment variables. Keep them out of your codebase.
Auto-Update Setup
Electron's auto-update is free and built in. Four requirements:
- App runs on macOS or Windows
- GitHub repo exists (public = no extra config; private = set
GH_TOKEN) - Builds published to GitHub Releases
- Builds are code signed
With electron-forge
npm install --save-dev @electron-forge/publisher-github
Enter fullscreen mode Exit fullscreen mode
forge.config.js:
module.exports = {
publishers: [
{
name: '@electron-forge/publisher-github',
config: {
repository: {
owner: 'your-github-username',
name: 'your-repo-name',
},
prerelease: false,
},
},
],
};
Enter fullscreen mode Exit fullscreen mode
package.json script:
{ "scripts": { "publish": "electron-forge publish" } }
Enter fullscreen mode Exit fullscreen mode
With electron-builder
npm install --save-dev electron-builder
Enter fullscreen mode Exit fullscreen mode
package.json:
{
"build": {
"appId": "com.yourcompany.yourapp",
"publish": {
"provider": "github",
"owner": "your-github-username",
"repo": "your-repo-name"
}
},
"scripts": {
"dist": "electron-builder",
"release": "electron-builder --publish always"
}
}
Enter fullscreen mode Exit fullscreen mode
Wiring Update Events in the App
const { autoUpdater } = require('electron-updater');
app.whenReady().then(() => {
autoUpdater.checkForUpdatesAndNotify();
});
// Tell the renderer when an update is ready
autoUpdater.on('update-downloaded', (info) => {
mainWindow.webContents.send('update-available', info.version);
});
// Install when the user confirms
ipcMain.on('install-update', () => {
autoUpdater.quitAndInstall();
});
Enter fullscreen mode Exit fullscreen mode
Renderer side:
ipcRenderer.on('update-available', (event, version) => {
const userConfirmed = confirm(`Version ${version} is ready. Restart to install?`);
if (userConfirmed) ipcRenderer.send('install-update');
});
Enter fullscreen mode Exit fullscreen mode
Always prompt — never force-quit mid-session.
GitHub Actions CI
Automate releases on every version tag:
name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, windows-latest]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- run: npm install
- name: Publish
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
CSC_LINK: ${{ secrets.CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
run: npm run release
Enter fullscreen mode Exit fullscreen mode
Tag a release (git tag v1.2.0 && git push --tags), the workflow fires, builds for both platforms, signs, and creates the GitHub Release. Running instances auto-update on the next check.
Things Worth Knowing Before You Ship
Full vs. delta updates — by default, the full installer downloads on each update. For large apps, configure delta updates to reduce download size.
Update channels — use GitHub pre-releases + electron-updater's channel option to run a beta channel before pushing to all users.
macOS notarization — required since Catalina, adds a few minutes to CI, needs Apple Developer account setup. Plan for it upfront.
Don't force-restart — quitAndInstall() is immediate. Always give users a chance to save their work first.
Building Desktop Apps with Electron?
At Innostax, we build with both — the choice comes from what the project actually needs, not what we happen to prefer. Whether electron-forge or electron-builder fits better depends on what the distribution requirements actually demand.
If you're figuring out the right stack for something you're building, innostax.com/contact is the right place to start that conversation.
Originally published on the Innostax Engineering Blog.
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.