# How to Build a Cross-Platform Desktop App with Electron and FastAPI: Architecture and Implementation Guide

> Build a cross-platform desktop app with Electron and FastAPI. Learn to run the FastAPI server as a subprocess, communicate via HTTP, and expose a type-checked API.

- Repository: [lightningpixel/modly](https://github.com/lightningpixel/modly)
- Tags: architecture
- Published: 2026-08-21

---

**To build a cross-platform desktop app with Electron and FastAPI, run the FastAPI server as a subprocess managed by Electron's main process, communicating over HTTP while exposing a secure, type-checked API to the renderer via a preload script.**

Building a cross-platform desktop app with Electron and FastAPI requires careful separation between the UI layer and the Python backend to prevent crashes in heavy inference tasks from affecting the frontend. The Modly repository demonstrates this pattern by embedding a FastAPI server within an Electron application, using a `PythonBridge` class to manage the subprocess lifecycle and health monitoring. This architecture enables native desktop capabilities with a React frontend while leveraging Python's ecosystem for AI-driven backend operations.

## Architecture of an Electron-FastAPI Desktop App

### Electron Main Process and Window Management

The Electron main process serves as the orchestration layer, bootstrapping both the native window and the Python backend. In [`electron/main/index.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/index.ts), the application creates a `BrowserWindow` instance and initializes the `PythonBridge` before the renderer loads.

The main process handles three critical responsibilities: window lifecycle management, IPC handler registration via `setupIpcHandlers()`, and auto-update initialization through `initAutoUpdater()`. By spawning the FastAPI server before the renderer appears, the UI can immediately reach the backend at `http://127.0.0.1:8765` without timing issues.

### Python Bridge Process Manager

The `PythonBridge` class in [`electron/main/python-bridge.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/python-bridge.ts) wraps the FastAPI subprocess, providing health monitoring and log forwarding. When `pythonBridge.start()` executes, it launches the server via `uvicorn` and polls the `/health` endpoint until the backend signals readiness.

This bridge implements platform-specific process termination logic at lines 68-73, using process groups on Unix systems and `taskkill` on Windows to guarantee clean shutdown. The bridge also captures stdout and stderr from the Python process, emitting log messages to the renderer through IPC to enable real-time debugging in the UI.

### Secure Preload Script API

Security isolation requires the renderer to access Node.js capabilities only through a carefully controlled preload layer. The [`electron/preload/electron-api.ts`](https://github.com/lightningpixel/modly/blob/main/electron/preload/electron-api.ts) file uses `contextBridge.exposeInMainWorld` to inject a `window.electron` object containing type-safe methods.

The exposed API includes filesystem operations like `window.electron.fs.readFileBase64()`, window controls such as `window.electron.window.minimize()`, and backend configuration via `window.electron.api.baseUrl`. This pattern prevents untrusted renderer code from executing arbitrary shell commands while maintaining full functionality.

### React Renderer Integration

The React components interact with the backend exclusively through the preloaded API. For example, [`src/shared/components/layout/TopBar.tsx`](https://github.com/lightningpixel/modly/blob/main/src/shared/components/layout/TopBar.tsx) invokes `window.electron.window.minimize()` and `window.electron.window.close()` to implement custom title bar controls.

Data fetching occurs through standard HTTP requests to the FastAPI server, with the preload script providing the base URL. Custom hooks like those in [`src/shared/hooks/useApi.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/hooks/useApi.ts) handle binary encoding and JSON serialization, calling endpoints such as `/workflow-runs/from-image` to trigger AI generation pipelines.

### FastAPI Backend Services

The Python layer resides in [`api/main.py`](https://github.com/lightningpixel/modly/blob/main/api/main.py), configuring CORS and mounting routers for generation tasks. The [`api/routers/generation.py`](https://github.com/lightningpixel/modly/blob/main/api/routers/generation.py) file defines REST endpoints that accept base64-encoded images and coordinate with the extension registry to execute model inference.

Unlike typical web applications, this FastAPI instance listens on localhost only, accepting connections exclusively from the Electron main process and renderer. The backend maintains stateless REST conventions while supporting long-running workflow executions that stream progress updates back to the UI.

## Cross-Platform Desktop Implementation

### Process Isolation and Stability

Running FastAPI as a separate subprocess isolates memory-intensive Python operations from the Chromium renderer process. If a model inference exhausts available RAM or triggers a segmentation fault, the `PythonBridge` detects the exit code and can restart the backend without crashing the Electron window.

This architecture also enables CPU throttling and resource monitoring; the main process tracks Python subprocess health independently of UI responsiveness, ensuring the native window controls remain fluid even during heavy computation.

### Platform-Specific Process Management

The `PythonBridge` handles cross-platform discrepancies in process termination. On macOS and Linux, it utilizes process groups to ensure child processes of the Python interpreter terminate alongside the main uvicorn process. On Windows, it explicitly invokes `taskkill` with the `/F` flag to prevent zombie processes when the user closes the application.

Platform detection also influences the build pipeline. Packaging scripts embed the Python virtual environment directly into the Electron bundle, creating platform-specific binaries that include the correct Python interpreter for Windows, Linux, or Apple Silicon macOS without requiring external installations.

### Auto-Updates and Extension Loading

The `initAutoUpdater()` function integrated in [`electron/main/index.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/index.ts) handles silent updates for both the Electron frontend and the Python backend. The extension system managed by [`api/services/generator_registry.py`](https://github.com/lightningpixel/modly/blob/main/api/services/generator_registry.py) supports hot-reloading of third-party generators from GitHub repositories containing [`manifest.json`](https://github.com/lightningpixel/modly/blob/main/manifest.json) files, allowing users to install AI models without restarting the application.

## Implementation Examples

### Spawning the FastAPI Server from Electron

```ts
// electron/main/index.ts (excerpt)
pythonBridge = new PythonBridge()
pythonBridge.setWindowGetter(() => mainWindow)
setupIpcHandlers(pythonBridge, () => mainWindow)   // registers IPC for window ops
initAutoUpdater(() => mainWindow)                 // auto‑update support
pythonBridge.start()                              // launches FastAPI via uvicorn

```

### Exposing Type-Safe APIs via Preload

```ts
// electron/preload/electron-api.ts (excerpt)
contextBridge.exposeInMainWorld('electron', {
  fs: {
    readFileBase64: async (path: string) => {
      const res = await ipcRenderer.invoke('fs:readFileBase64', path)
      return res as string
    }
  },
  window: {
    minimize: () => ipcRenderer.invoke('window:minimize')
  },
  api: {
    baseUrl: 'http://127.0.0.1:8765'   // injected by PythonBridge
  }
})

```

### Calling Backend Endpoints from React

```tsx
// src/shared/hooks/useApi.ts (excerpt)
export async function generateMesh(imagePath: string) {
  const base64 = await window.electron.fs.readFileBase64(imagePath)
  const resp = await fetch(`${window.electron.api.baseUrl}/workflow-runs/from-image`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ image: base64 })
  })
  return resp.json()
}

```

### Defining REST Routes in FastAPI

```python

# api/routers/generation.py (excerpt)

@router.post("/workflow-runs/from-image")
async def generate_from_image(payload: ImagePayload):
    # … invoke model extension, stream progress, return final mesh URL …

    return {"status": "completed", "mesh_url": mesh_path}

```

## Extension System and Hot Reloading

The [`generator_registry.py`](https://github.com/lightningpixel/modly/blob/main/generator_registry.py) service scans the user data directory for extension folders containing [`manifest.json`](https://github.com/lightningpixel/modly/blob/main/manifest.json) files, dynamically importing Python modules without restarting the FastAPI server. This enables third-party developers to distribute AI models as GitHub repositories that users can install through the UI.

The registry maintains an in-memory cache of available generators, refreshing the list when file system watchers detect new installations. This hot-reloading capability ensures the desktop app remains responsive while updating its backend capabilities, a critical feature for AI tools requiring frequent model updates.

## Developer Workflow and CLI Integration

Running `npm run dev` starts both the Electron main process and the FastAPI server in watch mode, enabling hot module replacement for the React frontend and auto-restart for the Python backend. For headless automation or CI pipelines, the [`tools/modly-cli/agent.py`](https://github.com/lightningpixel/modly/blob/main/tools/modly-cli/agent.py) script provides direct access to the FastAPI endpoints without launching the Electron window.

This dual-interface architecture allows developers to test backend logic through the CLI while building UI components against the same REST API, ensuring consistency between automated tests and desktop interactions.

## Summary

- **Process isolation** prevents Python crashes from affecting the Electron renderer by running FastAPI as a managed subprocess on port 8765.
- **Platform-specific termination logic** in [`electron/main/python-bridge.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/python-bridge.ts) ensures clean shutdown across Windows, Linux, and macOS.
- **Type-safe IPC** via [`electron/preload/electron-api.ts`](https://github.com/lightningpixel/modly/blob/main/electron/preload/electron-api.ts) exposes controlled filesystem and window APIs to the React renderer without security vulnerabilities.
- **Hot-reloading extension system** allows third-party model installation without restarting the application, managed through [`api/services/generator_registry.py`](https://github.com/lightningpixel/modly/blob/main/api/services/generator_registry.py).
- **Unified development workflow** supports both desktop GUI usage and headless CLI testing through the same FastAPI backend.

## Frequently Asked Questions

### How do you spawn a FastAPI server from an Electron app?

Instantiate the `PythonBridge` class in [`electron/main/index.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/index.ts) and call `pythonBridge.start()`, which launches the server via `uvicorn` and polls the `/health` endpoint until the backend signals readiness. The bridge then injects the base URL into the renderer context, allowing immediate API access once the window appears.

### How does the renderer securely communicate with the FastAPI backend?

The preload script at [`electron/preload/electron-api.ts`](https://github.com/lightningpixel/modly/blob/main/electron/preload/electron-api.ts) exposes a `window.electron` API using `contextBridge.exposeInMainWorld`, restricting the React renderer to specific IPC channels while blocking direct Node.js access. The renderer fetches data from `http://127.0.0.1:8765` or invokes IPC methods for privileged operations like filesystem access.

### How do you handle cross-platform process management when bundling Python with Electron?

The `PythonBridge` implementation uses platform detection to apply Unix process groups or Windows `taskkill` commands, ensuring the Python subprocess terminates cleanly when the user exits the application. Packaging scripts embed the Python virtual environment into the Electron binary, eliminating external dependencies on end-user machines.

### Can you extend the FastAPI backend without restarting the Electron app?

Yes, the extension registry in [`api/services/generator_registry.py`](https://github.com/lightningpixel/modly/blob/main/api/services/generator_registry.py) dynamically loads third-party extensions from the user data folder and refreshes the generator list without requiring a FastAPI restart. This enables users to install new AI models from GitHub repositories and immediately use them in the desktop interface.