# How the Python Bridge Connects Electron Main Process to FastAPI Backend in Modly

> Discover how the Python bridge in Modly connects Electron main process to a FastAPI backend. Learn about child process spawning, health checks, and seamless API communication.

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

---

**The Python Bridge in [`electron/main/python-bridge.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/python-bridge.ts) spawns a FastAPI server as a child process, verifies its health via HTTP polling, then routes API calls over standard HTTP while forwarding logs and crash events to the renderer through Electron IPC.**

The **Python Bridge** is the critical communication layer in the **Modly** 3-D generation application (lightningpixel/modly). It enables the Electron-based frontend to interact with the Python-powered FastAPI backend that handles model inference, optimization, and generation tasks. Unlike typical Electron apps that embed Python directly, Modly uses a **child process + HTTP** architecture that keeps the Python runtime isolated but fully integrated with the UI lifecycle.

## How the Python Bridge Spawns the FastAPI Server

When the Electron app initializes, the `PythonBridge.start()` method in [`electron/main/python-bridge.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/python-bridge.ts) creates a detached child process running **Uvicorn**:

```bash
uvicorn main:app --host 127.0.0.1 --port 8765

```

The bridge resolves the Python interpreter from either the bundled virtual environment or a local `.venv` during development. It injects critical environment variables—`MODELS_DIR`, `WORKSPACE_DIR`, `EXTENSIONS_DIR`, and the Hugging Face token—so FastAPI can locate models and user data without hardcoded paths.

```typescript
// electron/main/index.ts
import { PythonBridge } from './python-bridge';
const pythonBridge = new PythonBridge();

// Provide window reference for IPC messaging
pythonBridge.setWindowGetter(() => mainWindow);

app.on('ready', async () => {
  await pythonBridge.start();          // launches FastAPI subprocess
  console.log('FastAPI ready on', pythonBridge.getPort());
});

```

## Health Check Handshake Before HTTP Communication

After spawning the process, the bridge enters a **polling loop** to verify FastAPI readiness. It repeatedly sends HTTP GET requests to `http://127.0.0.1:8765/health` using **axios** until receiving a 200 response. Only then does `this.ready` become `true`, signalling that the Electron renderer can safely issue API calls.

This synchronous-style initialization prevents race conditions where the UI might attempt generation before the model layers are loaded.

## Electron to FastAPI Communication Over HTTP

Once healthy, all communication flows through **standard HTTP requests** to FastAPI endpoints like `/generation`, `/model`, and `/optimize`. The UI uses the `useApi` hook with `API_BASE_URL` exported from the bridge:

```typescript
// src/shared/hooks/useApi.ts
import { API_BASE_URL } from '../../electron/main/python-bridge';

export async function apiGet<T>(path: string): Promise<T> {
  const response = await fetch(`${API_BASE_URL}${path}`);
  if (!response.ok) throw new Error(`API error ${response.status}`);
  return response.json() as Promise<T>;
}

// Example: verify backend health
const health = await apiGet<{status: string}>('/health');

```

This design decouples the frontend from Electron's IPC for data operations, leveraging FastAPI's async capabilities and OpenAPI auto-documentation directly.

## Forwarding Logs and Crash Events via Electron IPC

The bridge monitors the FastAPI child's **stdout** and **stderr** streams, routing them through two channels:

- **`python:log`** – Real-time log lines forwarded to the renderer for the in-app console
- **`python:crashed`** – Exit code notification when the FastAPI process terminates unexpectedly

```typescript
// electron/preload/electron-api.ts
import { ipcRenderer } from 'electron';

export const onPythonLog = (callback: (msg: string) => void) => {
  ipcRenderer.on('python:log', (_event, msg) => callback(msg));
};

export const onPythonCrashed = (callback: (info: {code: number}) => void) => {
  ipcRenderer.on('python:crashed', (_event, info) => callback(info));
};

```

Non-info log levels trigger UI alerts, while crash events enable automatic recovery dialogs or restart prompts.

## Graceful Shutdown and Memory Management

The `PythonBridge.stop()` method ensures clean termination by killing the **entire process group** (or using `taskkill` on Windows), capturing any extension-launched subprocesses. The `restart()` sequence—stop, then start—provides a memory leak mitigation strategy after heavy generation runs:

```typescript
// src/shared/hooks/useGeneration.ts
import { pythonBridge } from '../../electron/main/python-bridge';

export async function freeMemoryAndRestart() {
  await pythonBridge.restart();   // fresh FastAPI instance
}

```

This pattern is essential for long-running sessions where model weights and CUDA contexts accumulate.

## Summary

- **Child process spawning**: `PythonBridge.start()` launches Uvicorn with environment-injected configuration in [`electron/main/python-bridge.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/python-bridge.ts)
- **Health verification**: Axios polling against `/health` gates readiness before HTTP traffic begins
- **API communication**: Standard `fetch` requests via `API_BASE_URL` in [`useApi.ts`](https://github.com/lightningpixel/modly/blob/main/useApi.ts), not Electron IPC
- **Event streaming**: `python:log` and `python:crashed` IPC channels surface backend state to the renderer
- **Lifecycle control**: Process-group-aware shutdown and restart capabilities prevent resource leaks

## Frequently Asked Questions

### What IPC channels does the Python Bridge use?

The bridge uses two primary channels defined in [`electron/preload/electron-api.ts`](https://github.com/lightningpixel/modly/blob/main/electron/preload/electron-api.ts): `python:log` streams stdout/stderr lines for the in-app console, and `python:crashed` signals unexpected FastAPI termination with the exit code. API data itself travels over HTTP, not IPC.

### How does the bridge find the correct Python interpreter?

It checks for a bundled virtual environment first, then falls back to `.venv` in development mode. This dual-path resolution ensures consistent behavior across packaged builds and local development without code changes.

### Why use HTTP instead of Electron's native IPC for API calls?

HTTP enables FastAPI's automatic OpenAPI documentation, async request handling, and standard web debugging tools. It also keeps the architecture portable—FastAPI could theoretically run on a remote GPU server without modifying the frontend communication pattern.

### What happens if the FastAPI server crashes during a generation?

The bridge emits `python:crashed` with the exit code through IPC, allowing the renderer to display an error dialog. The UI can then call `pythonBridge.restart()` to spawn a fresh instance without requiring an app restart, preserving unsaved user state in the Electron layer.