# How Python Backend and Electron Processes Communicate via IPC in LifeTrace

> Discover how LifeTrace enables seamless communication between Python backend and Electron processes using a local HTTP REST API for business logic and IPC for UI messaging.

- Repository: [FreeU-group/lifetrace](https://github.com/freeu-group/lifetrace)
- Tags: internals
- Published: 2026-03-02

---

**LifeTrace uses Electron's IPC channel exclusively for UI-to-main-process messaging, while all business logic communication between the Electron main process and the Python FastAPI backend occurs over a local HTTP REST API.**

The freeu-group/lifetrace repository implements a clean separation between the user interface and core logic by combining an Electron frontend with a Python FastAPI backend. Understanding how these two distinct runtimes communicate is essential for developers extending the application's functionality or debugging cross-process issues.

## Architecture Overview: IPC vs HTTP

LifeTrace employs a two-layer communication strategy that keeps the **Electron IPC** separate from the **HTTP REST API**. The data flow follows this path:

1. **Renderer Process** (React/Next.js) ↔ **Main Process** (Node.js): Uses Electron's `ipcRenderer` and `ipcMain` modules for UI-level events like notifications and window controls.
2. **Main Process** ↔ **Python Backend** (FastAPI/uvicorn): Uses standard HTTP requests to REST endpoints exposed by the Python server.

This design keeps the runtimes loosely coupled, allowing the Python backend to run independently for testing or alternative clients.

## Electron IPC Layer (Renderer to Main)

The renderer process never communicates directly with Python. Instead, it sends messages to the main process via the preload script bridge defined in [`free-todo-frontend/electron/preload.ts`](https://github.com/freeu-group/lifetrace/blob/main/free-todo-frontend/electron/preload.ts).

In [`free-todo-frontend/electron/ipc-handlers.ts`](https://github.com/freeu-group/lifetrace/blob/main/free-todo-frontend/electron/ipc-handlers.ts), the main process registers handlers using `ipcMain.handle` and `ipcMain.on` to receive UI events:

```typescript
// In ipc-handlers.ts (main process)
ipcMain.handle(
  "show-notification",
  async (_event, data: NotificationData) => {
    logger.info(`Received notification request: ${data.id}`);
    showSystemNotification(data, windowManager);
  },
);

```

The renderer invokes these handlers through the exposed `electronAPI`:

```typescript
// In a React component (renderer)
window.electronAPI?.showNotification({
  id: "msg-1",
  title: "Todo captured",
  content: "Your screenshot was processed",
  timestamp: new Date().toISOString(),
});

```

## HTTP Communication Bridge (Main to Python)

All business logic requests flow from the main process to the Python backend via HTTP. The `BackendServer` class in [`free-todo-frontend/electron/backend-server.ts`](https://github.com/freeu-group/lifetrace/blob/main/free-todo-frontend/electron/backend-server.ts) manages the Python process lifecycle and provides the base URL for requests.

When the main process needs to send data—such as a screenshot for OCR—it constructs an HTTP POST request to the FastAPI endpoint. This pattern is implemented in [`free-todo-frontend/electron/ipc-handlers-todo-capture.ts`](https://github.com/freeu-group/lifetrace/blob/main/free-todo-frontend/electron/ipc-handlers-todo-capture.ts):

```typescript
// Main-process handler (ipc-handlers-todo-capture.ts)
ipcMain.handle("capture-and-extract-todos", async (_event, panelBounds) => {
  // 1️⃣ Capture screen with desktopCapturer
  // 2️⃣ Convert image → base64
  // 3️⃣ Build backend URL from BackendServer
  const backendUrl = getBackendUrl();          // from backend-server.ts
  const apiUrl = `${backendUrl}/api/floating-capture/extract-todos`;
  // 4️⃣ POST base64 image to FastAPI using net.request
  const response = await sendToBackend(apiUrl, base64Data, true);
  return response;
});

```

The `sendToBackend` function uses Electron’s `net.request` API (a wrapper around Node’s HTTP client) to transmit data to the Python server.

## Python FastAPI Backend Endpoints

The Python side exposes standard REST endpoints using FastAPI. In [`lifetrace/routers/floating_capture.py`](https://github.com/freeu-group/lifetrace/blob/main/lifetrace/routers/floating_capture.py), the endpoint receiving the screenshot data is defined as:

```python

# lifetrace/routers/floating_capture.py

@router.post("/floating-capture/extract-todos")
async def extract_todos(payload: ExtractPayload):
    # payload.image_base64 contains the screenshot

    # ... run OCR / LLM extraction ...

    return {
        "success": True,
        "extracted_todos": [...],
        "created_count": 3,
    }

```

Additional business logic endpoints are located in [`lifetrace/routers/todo.py`](https://github.com/freeu-group/lifetrace/blob/main/lifetrace/routers/todo.py) and other router modules, all following the same HTTP request/response pattern.

## Backend Lifecycle and Port Management

The `BackendServer` class in [`free-todo-frontend/electron/backend-server.ts`](https://github.com/freeu-group/lifetrace/blob/main/free-todo-frontend/electron/backend-server.ts) handles the complexity of starting and connecting to the Python process.

When the Electron app launches, it first attempts to detect an already-running backend via the `detectRunningBackendPort()` method, which probes the `/health` endpoint on potential ports:

```typescript
// BackendServer.detectRunningBackendPort() implementation
async detectRunningBackendPort(): Promise<number | null> {
  // Probes /health endpoint to find existing Python server
  // Returns port if found, null otherwise
}

```

If no server is found, `BackendServer.start()` spawns a new Python process:

```typescript
// BackendServer.start() (partial)
this.resolveBackendPaths();               // finds entry script / venv
await ensurePythonRuntime(...);           // creates venv if needed
this.port = await portManager.findAvailablePort(...);
const spawnCommand = this.backendRuntime === "pyinstaller"
    ? this.backendEntryScript
    : this.venvPythonPath;
const spawnArgs = this.backendRuntime === "pyinstaller"
    ? backendArgs
    : [this.backendEntryScript, ...backendArgs];
this.process = spawn(spawnCommand, spawnArgs, { cwd: this.backendSourceDir });

```

The Python server runs independently on the dynamically allocated port, exposing the REST API that the main process consumes via HTTP.

## Summary

- **Electron IPC is UI-only**: The renderer process communicates with the main process via `ipcRenderer` and `ipcMain` handlers defined in [`free-todo-frontend/electron/ipc-handlers.ts`](https://github.com/freeu-group/lifetrace/blob/main/free-todo-frontend/electron/ipc-handlers.ts) for window management and notifications, not for business logic.
- **HTTP bridges the gap**: All data exchange between the Electron main process and the Python FastAPI backend occurs over local HTTP REST APIs, not through IPC channels.
- **Port discovery is dynamic**: The `BackendServer` class in [`free-todo-frontend/electron/backend-server.ts`](https://github.com/freeu-group/lifetrace/blob/main/free-todo-frontend/electron/backend-server.ts) handles spawning the Python process and discovers or assigns ports via health-check probes to the `/health` endpoint.
- **Python exposes REST endpoints**: FastAPI routers in `lifetrace/routers/` (such as [`floating_capture.py`](https://github.com/freeu-group/lifetrace/blob/main/floating_capture.py)) receive HTTP POST requests containing base64-encoded screenshots and return extracted data as JSON.

## Frequently Asked Questions

### Does LifeTrace use Electron's IPC for Python communication?

No. Electron's IPC (`ipcMain` and `ipcRenderer`) is used exclusively for communication between the renderer process (React/Next.js UI) and the main process (Node.js). The main process communicates with the Python FastAPI backend via standard HTTP requests to local REST endpoints, not through IPC channels.

### How does the app find the Python backend port?

When the Electron app starts, the `BackendServer` class attempts to locate an already-running Python server by calling `detectRunningBackendPort()`. This method probes the `/health` endpoint on a range of potential ports. If a running instance is found, Electron connects to it; otherwise, `BackendServer.start()` spawns a new Python process on a dynamically assigned free port.

### Can the Python backend run independently of Electron?

Yes. The Python FastAPI backend is designed to run as a standalone server using `uvicorn`. Because the Electron main process communicates via HTTP REST APIs, you can start the Python server manually (e.g., `python -m lifetrace.server`) and interact with it using `curl`, a web browser, or any other HTTP client without launching the Electron frontend.

### What protocol does the main process use to send screenshots to Python?

The main process sends screenshots to the Python backend using HTTP POST requests. In [`free-todo-frontend/electron/ipc-handlers-todo-capture.ts`](https://github.com/freeu-group/lifetrace/blob/main/free-todo-frontend/electron/ipc-handlers-todo-capture.ts), the `sendToBackend` function uses Electron's `net.request` API (a Node.js HTTP client wrapper) to POST base64-encoded image data to endpoints like `/api/floating-capture/extract-todos` defined in [`lifetrace/routers/floating_capture.py`](https://github.com/freeu-group/lifetrace/blob/main/lifetrace/routers/floating_capture.py).