Web to Desktop
Main and renderer
Lesson 1 of 5
What you'll learn
- Understand the main process vs renderer process split
- Create a window with the
appandBrowserWindowlifecycle - See why renderers are sandboxed browser pages while main is Node
An Electron app is not "a website with a frame around it." It is two kinds of OS process with very different powers. There is exactly one main process — a Node.js program with full OS access that starts first, owns the application lifecycle, and creates windows. Each window then hosts a renderer process — a Chromium page that runs your HTML, CSS, and frontend JavaScript, and nothing more.
// main.js — the main process. Plain Node.js (ESM works fine too).
const { app, BrowserWindow } = require("electron");
function createWindow() {
const win = new BrowserWindow({ width: 900, height: 600 });
win.loadFile("index.html"); // this page becomes a renderer process
}
app.whenReady().then(() => {
createWindow();
// macOS: clicking the Dock icon with no windows open makes a new one
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
// Windows/Linux apps quit with their last window; macOS apps stay running
app.on("window-all-closed", () => {
if (process.platform !== "darwin") app.quit();
});
That's the whole lifecycle skeleton: wait for app.whenReady(), construct a BrowserWindow, load a page into it. Close every window and window-all-closed fires; on macOS the app lingers (matching platform convention), elsewhere it quits.
Who owns what
The main process owns everything the OS gives a native app: the filesystem, spawning processes, windows, menus, the tray, dialogs, app quit. The renderer owns the DOM and your UI framework — and by default it is a sandboxed browser page: sandbox: true and contextIsolation: true are the modern defaults, nodeIntegration is off, and there is no require, no fs, no Node globals in your page code.
Why so strict? A renderer displays web content, and web content can be compromised — one XSS in a page that had Node access would be remote code execution on your user's machine. So Electron keeps the renderer as locked down as a browser tab, and anything privileged happens by asking the main process over IPC (next lesson's topic).
One main, many renderers
Every BrowserWindow (and every <iframe> from another origin) gets its own renderer process, but there is only ever one main process. Think of main as the server and each window as a browser tab that happens to ship inside your app.
The challenge is a JS model of the split: two EventEmitter-backed "processes" where the renderer has no OS powers and must route every capability request through main.
Run it. The renderer can't touch the filesystem — it emits requests, and main answers only for capabilities it actually owns.
Which process owns OS-level capabilities like the filesystem and app lifecycle?
Next: preload scripts and contextBridge — the narrow, safe doorway between those two worlds.
Saved on this device. Sign in to sync your progress everywhere.