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

The Python Bridge in 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 creates a detached child process running Uvicorn:

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.

// 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:

// 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
// 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:

// 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
  • Health verification: Axios polling against /health gates readiness before HTTP traffic begins
  • API communication: Standard fetch requests via API_BASE_URL in 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: 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →