Electron API Demos: Hands-On Examples for Building Desktop Apps
Building cross-platform desktop apps with Electron is fast and practical, but understanding its APIs makes the difference between a prototype and a production-quality app. This article walks through hands-on examples using core Electron APIs you’ll use most often: window creation, menus, IPC (main ↔ renderer), native dialogs, and system integrations like notifications and the tray. Each example includes a short goal, key code snippets, and quick notes on best practices.
Prerequisites
- Node.js installed (LTS recommended)
- Basic knowledge of JavaScript and npm
- An existing Electron project scaffolded (npx create-electron-app or manual setup)
1) Create the main window (BrowserWindow)
Goal: Open a resizable app window and load your frontend.
Key code (main process: main.js):
const { app, BrowserWindow } = require(‘electron’); function createWindow() { const win = new BrowserWindow({ width: 1024, height: 768, webPreferences: { nodeIntegration: false, contextIsolation: true, preload: _dirname + ‘/preload.js’ } }); win.loadFile(‘index.html’);} app.whenReady().then(createWindow);app.on(‘window-all-closed’, () => { if (process.platform !== ‘darwin’) app.quit(); });
Notes: Use contextIsolation and a preload script to keep renderer code secure. Manage window state (size/position) via a small persistence layer if desired.
2) Native menus and accelerators
Goal: Add an application menu with keyboard shortcuts.
Key code (main process):
const { Menu } = require(‘electron’); const template = [ { label: ‘File’, submenu: [ { label: ‘Open’, accelerator: ‘CmdOrCtrl+O’, click: () => {/open logic */} }, { role: ‘quit’ } ] }, { role: ‘editMenu’ }, { role: ‘viewMenu’ }]; const menu = Menu.buildFromTemplate(template);Menu.setApplicationMenu(menu);
Notes: Use predefined roles for standard behavior across platforms. Localize labels where needed.
3) IPC: Secure main ↔ renderer communication
Goal: Exchange messages and data between renderer and main safely.
Preload (preload.js):
const { contextBridge, ipcRenderer } = require(‘electron’); contextBridge.exposeInMainWorld(‘api’, { send: (channel, data) => ipcRenderer.invoke(channel, data), receive: (channel, cb) => ipcRenderer.on(channel, (e, …args) => cb(…args))});
Main (main.js):
const { ipcMain, dialog } = require(‘electron’); ipcMain.handle(‘select-file’, async () => { const result = await dialog.showOpenDialog({ properties: [‘openFile’] }); return result.filePaths || [];});
Renderer (renderer.js):
async function chooseFile() { const paths = await window.api.send(‘select-file’); console.log(paths);}
Notes: Prefer ipcMain.handle/invoke for request/response patterns. Sanitize and validate any data before processing.
4) Native dialogs (open/save/alert)
Goal: Let users choose files or confirm actions using native UI.
Example (main process):
const { dialog } = require(‘electron’); ipcMain.handle(‘save-file’, async (, { defaultPath }) => { const { canceled, filePath } = await dialog.showSaveDialog({ defaultPath }); if (canceled) return null; return filePath;});
Notes: Always call dialogs from the main process. Use filters for file types.
5) Notifications and tray icon
Goal: Show system notifications and run a background tray icon with a menu.
Notification (renderer or main with permission):
new Notification({ title: ‘Sync Complete’, body: ‘All files uploaded.’ }).show();
Tray (main process):
const { Tray, Menu } = require(‘electron’);const tray = new Tray(‘assets/icon.png’);tray.setToolTip(‘My App’);tray.setContextMenu(Menu.buildFromTemplate([ { label: ‘Open’, click: () => {/* show window */} }, { label: ‘Quit’, role: ‘quit’ }]));
Notes: Respect platform differences (Windows/Linux/macOS) for icon sizes and notification behavior.
6) File system access, auto-updates, and native integration
Goal: Read/write files and keep your app updated.
- Use Node fs in the main process or via secure IPC. Validate paths and never allow arbitrary writes without checks.
- Use electron-updater or Squirrel (depending on platform) for auto-update flows; test updates on staging channels.
- Integrate with OS features (deep links, protocol handlers, file associations) via app.setAsDefaultProtocolClient and relevant manifest settings.
7) Packaging and distribution
Goal: Prepare your app for users.
- Use electron-builder or electron-forge to create platform-specific installers (DMG, EXE, AppImage).
- Code-sign your app for macOS and Windows to avoid warnings and enable smooth updates.
- Test on real OS versions (not just development machine).
Best practices summary
- Security: enable contextIsolation, disable nodeIntegration in renderer, use a preload bridge, validate IPC inputs.
- Performance: lazy-load heavy modules, avoid blocking the main process, move CPU work to worker threads if necessary.
- Reliability: handle uncaught exceptions in main/renderer, persist user state, provide clear error reporting.
- UX: follow platform conventions for menus, keyboard shortcuts, and window behavior.
Next steps (practical exercise)
- Scaffold an app with create-electron-app.
- Implement a BrowserWindow with a preload exposing a single file-picker API.
- Add a tray icon with a menu item that shows the window.
- Package the app with electron-builder and test on one other OS.
These examples give you a practical foundation to build, secure, and distribute real desktop apps with Electron. Use them as templates, expand each stub into features you need, and consult Electron docs for deeper API details.
Leave a Reply