Electron Desktop Apps — Complete API Reference & Guide
Electron is a framework for building cross-platform desktop applications using web technologies — HTML, CSS, and JavaScript — running on the Chromium engine and Node.js runtime.
What You’ll Learn
By the end of this tutorial, you’ll understand Electron’s two-process architecture, set up IPC communication between main and renderer processes, create BrowserWindows, use native dialogs and menus, and package your app for Windows, macOS, and Linux.
Why Electron Matters
Electron lets web developers build native desktop apps with their existing skills. VS Code, Slack, Discord, Figma, and thousands of other applications run on Electron. It provides native OS integration (system tray, notifications, file dialogs) while allowing you to use standard web technologies. DodaZIP, DodaTech’s file compression tool, uses Electron for its cross-platform desktop interface.
Electron Reference
Main Process
The main process creates browser windows and handles system-level interactions:
const { app, BrowserWindow, ipcMain, dialog } = require("electron");
let mainWindow;
app.whenReady().then(() => {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
nodeIntegration: false, // security: no Node.js in renderer
contextIsolation: true, // security: separate contexts
preload: path.join(__dirname, "preload.js")
}
});
mainWindow.loadFile("index.html");
});Line-by-line:
app.whenReady()— wait for Electron to finish initialization before creating windowsnew BrowserWindow({...})— creates a new native windownodeIntegration: false— renderer can’t access Node.js APIs (security best practice)contextIsolation: true— renderer code runs in an isolated context (security best practice)preload: path.join(__dirname, "preload.js")— a bridge script that runs before the renderer, used to expose specific APIs safely
The Two-Process Architecture:
flowchart LR
A[Main Process<br/>Node.js] -->|ipcMain| B[Preload Script<br/>Bridge]
B -->|contextBridge| C[Renderer Process<br/>Browser Window]
A --> D[Native APIs<br/>Menu, Dialog, Tray]
C --> E[Web UI<br/>HTML, CSS, JS]
IPC Communication
Electron keeps main and renderer processes separate for security. Communication happens through IPC (Inter-Process Communication):
// preload.js — expose safe APIs to renderer
const { contextBridge, ipcRenderer } = require("electron");
contextBridge.exposeInMainWorld("electronAPI", {
getData: () => ipcRenderer.invoke("get-data"),
saveFile: (data) => ipcRenderer.invoke("save-file", data)
});// main.js — handle requests from renderer
ipcMain.handle("get-data", async () => {
return await db.findMany();
});
ipcMain.handle("save-file", async (event, data) => {
const result = await dialog.showSaveDialog(mainWindow);
if (!result.canceled) {
fs.writeFileSync(result.filePath, JSON.stringify(data));
}
});Line-by-line:
contextBridge.exposeInMainWorld("electronAPI", {...})— safely exposes awindow.electronAPIobject to the renderer. The renderer callswindow.electronAPI.getData()which triggersipcRenderer.invoke("get-data")ipcRenderer.invoke("get-data")— sends a message to the main process and returns a PromiseipcMain.handle("get-data", async () => {...})— main process listens for “get-data” requests and respondsdialog.showSaveDialog(mainWindow)— opens the native OS save-file dialog
Why this architecture? In early Electron versions, the renderer had full Node.js access — but this was a security nightmare (any XSS vulnerability in the UI could execute system commands). Modern Electron uses context isolation and preload scripts to expose only specific, safe APIs.
Key APIs
| Module | Purpose |
|---|---|
app | Application lifecycle (ready, quit, activate) |
BrowserWindow | Create and manage native windows |
ipcMain / ipcRenderer | Process communication |
dialog | Native file dialogs (open, save, message) |
Menu | Native application menus and context menus |
Tray | System tray icons with context menus |
Notification | Desktop notifications |
shell | Open files/URLs with OS defaults |
clipboard | System clipboard (read/write) |
nativeImage | Create tray/app icons from images |
screen | Display information (resolution, scaling) |
Native Menus
const { Menu } = require("electron");
const template = [
{ label: "File",
submenu: [
{ label: "Open", accelerator: "CmdOrCtrl+O", click: () => openFile() },
{ type: "separator" },
{ label: "Quit", accelerator: "CmdOrCtrl+Q", role: "quit" }
]
},
{ label: "Edit",
submenu: [
{ role: "undo" },
{ role: "redo" }
]
}
];
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);Packaging
npm install --save-dev electron-builder
# Build for all platforms
npx electron-builder --win --mac --linux
# Platform-specific
npx electron-builder --win # Windows installer
npx electron-builder --mac # macOS .dmg
npx electron-builder --linux # Linux AppImage / debWhat packaging does:
- Bundles your app code with the Electron runtime
- Creates platform-specific installers (.exe, .dmg, .AppImage)
- Handles code signing for macOS and Windows
- Manages auto-update infrastructure
Common Mistakes
1. Enabling nodeIntegration in production
Setting nodeIntegration: true exposes Node.js APIs to the renderer. If your app has any XSS vulnerability, attackers get full system access. Always set it to false.
2. Forgetting contextIsolation
Without contextIsolation: true, the preload script and renderer share the same JavaScript context — defeating the security benefits of the preload pattern.
3. Using sync IPC in performance-critical paths
ipcRenderer.sendSync() blocks the renderer until the main process responds. Use async ipcRenderer.invoke() instead.
4. Not handling app quit properly
On macOS, apps should stay open when all windows close (common behavior). Handle the window-all-closed event correctly per platform.
5. Building for one platform and expecting it to work everywhere
Each OS has different path conventions, file dialogs, and system APIs. Always test on all target platforms.
Practice Questions
1. Why is nodeIntegration: false important?
Answer: It prevents the renderer process from accessing Node.js APIs, protecting against XSS attacks that could execute system commands.
2. What is the role of the preload script?
Answer: The preload script runs before the renderer and uses contextBridge to safely expose specific main-process APIs to the renderer.
3. How do main and renderer processes communicate?
Answer: Through IPC — ipcRenderer.invoke() in the renderer sends a request, ipcMain.handle() in the main process handles it and returns a response.
4. What does electron-builder do?
Answer: It packages your Electron app into platform-specific installers (.exe, .dmg, .AppImage) with the Electron runtime bundled.
Challenge
Build a simple text editor with Electron: main process manages file I/O via IPC, renderer provides the editing UI, preload exposes safe APIs. Package it for your platform and test the native save/open dialogs.
FAQ
Try It Yourself
# 1. Create a minimal Electron app
mkdir my-electron-app && cd my-electron-app
npm init -y
# 2. Install Electron
npm install --save-dev electron
# 3. Create main.js
cat > main.js << 'EOF'
const { app, BrowserWindow } = require("electron");
app.whenReady().then(() => {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: false,
contextIsolation: true
}
});
win.loadFile("index.html");
});
EOF
# 4. Create index.html
cat > index.html << 'EOF'
<!DOCTYPE html>
<html>
<head><title>My Electron App</title></head>
<body>
<h1>Hello from Electron!</h1>
<p>This is a desktop app built with web technologies.</p>
</body>
</html>
EOF
# 5. Add start script to package.json
# (manually add: "start": "electron .")
# 6. Run your app
npx electron .What’s Next
Explore more development tools:
| Topic | Description |
|---|---|
| https://tutorials.dodatech.com/tools/rxjs/ | Reactive programming with Observables |
| https://tutorials.dodatech.com/tools/lodash/ | JavaScript utility library |
Related topics to explore:
- JavaScript for Desktop
- Node.js System APIs
- Cross-Platform Packaging
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro