# How RomM Frontend Communicates with the Backend Using REST and WebSockets

> Discover how RomM's Vue 3 frontend uses Axios for REST APIs and Socket.IO for WebSockets to interact with its FastAPI backend. Learn about authentication and connection management.

- Repository: [The RomM Project/romm](https://github.com/rommapp/romm)
- Tags: how-to-guide
- Published: 2026-07-06

---

**RomM's Vue 3 frontend communicates with its FastAPI backend through Axios for REST API operations and Socket.IO for real-time WebSocket events, with centralized service wrappers handling authentication, CSRF protection, and connection lifecycle management.**

The RomM game library manager implements a clean separation between its Vue 3 single-page application frontend and Python backend using two complementary communication channels. Understanding how the RomM frontend communicates with the backend requires examining the Axios-based REST client for CRUD operations and the Socket.IO implementation for live push notifications. This architecture enables efficient data fetching while supporting real-time features like library scan progress and log streaming.

## REST API Communication via Axios

RomM centralizes all HTTP communication in a configured Axios instance that handles authentication headers, request tracking, and error states.

### Centralized Axios Client Configuration

The REST client resides in [[`frontend/src/services/api/index.ts`](https://github.com/rommapp/romm/blob/main/frontend/src/services/api/index.ts)](https://github.com/rommapp/romm/blob/master/frontend/src/services/api/index.ts) and exports a pre-configured Axios instance:

```ts
import axios from "axios";
import Cookies from "js-cookie";

const api = axios.create({
  baseURL: "/api",
  timeout: 120000,
  paramsSerializer: { … }
});

```

The **base URL** of `/api` ensures all requests route through the Vite dev server proxy to the FastAPI backend. A **CSRF token** is automatically injected into every request via the `x-csrftoken` header, retrieved from the `romm_csrftoken` cookie using the `js-cookie` library.

### Request Interceptors and Network State

The Axios instance implements request and response interceptors to track global network activity and handle authentication:

```ts
// Request interceptor
api.interceptors.request.use((config) => {
  inflightRequests.add(config.url);
  config.headers["x-csrftoken"] = Cookies.get("romm_csrftoken");
  return config;
});

// Response interceptor
api.interceptors.response.use(
  (response) => {
    inflightRequests.delete(response.config.url);
    document.dispatchEvent(new CustomEvent("backend-online"));
    if (inflightRequests.size === 0) networkQuiesced();
    return response;
  },
  (error) => {
    // Error handling and backend state tracking
    return Promise.reject(error);
  }
);

```

The **`inflightRequests`** Set tracks active HTTP calls, enabling the UI to display loading states only when necessary. When the set empties, the system emits a `network-quiesced` event, allowing components to hide global spinners. Error responses with status codes ≥ 500 trigger `backend-suspect` flags, while 4xx responses maintain the `backend-online` state.

### Fetching ROM Data

Components interact with the REST API through Pinia stores that call typed service functions. The [[`frontend/src/services/api/rom.ts`](https://github.com/rommapp/romm/blob/main/frontend/src/services/api/rom.ts)](https://github.com/rommapp/romm/blob/master/frontend/src/services/api/rom.ts) module provides the interface:

```ts
import api from "./index";

export async function getRoms(params: Record<string, any>) {
  const { data } = await api.get("/roms", { params });
  return data;
}

```

The [[`frontend/src/stores/roms.ts`](https://github.com/rommapp/romm/blob/main/frontend/src/stores/roms.ts)](https://github.com/rommapp/romm/blob/master/frontend/src/stores/roms.ts) store consumes this service:

```ts
import { getRoms } from "@/services/api/rom";

export const useRomsStore = defineStore("roms", {
  state: () => ({ allRoms: [] as SimpleRom[] }),
  actions: {
    async fetchRoms(filterParams) {
      const result = await getRoms(filterParams);
      this.allRoms = result.roms;
    },
  },
});

```

All REST responses are **type-safe** through TypeScript interfaces generated from the backend OpenAPI schema, located in `frontend/src/__generated__/models/`.

## WebSocket Communication via Socket.IO

For real-time functionality, RomM uses Socket.IO to establish persistent connections that support bidirectional event streaming.

### Socket.IO Client Configuration

The WebSocket client is configured in [[`frontend/src/services/socket.ts`](https://github.com/rommapp/romm/blob/main/frontend/src/services/socket.ts)](https://github.com/rommapp/romm/blob/master/frontend/src/services/socket.ts):

```ts
import { io } from "socket.io-client";

export default io({
  path: "/ws/socket.io/",
  transports: ["websocket", "polling"],
  autoConnect: false,
});

```

The **path** `/ws/socket.io/` matches the FastAPI backend's WebSocket endpoint mount point. Setting **`autoConnect`** to `false` allows the UI to establish connections only when specific real-time features are active, conserving resources when idle. The transport fallback ensures compatibility by attempting WebSocket first, then reverting to HTTP polling if necessary.

### Real-Time Event Handling

Components manually manage the socket lifecycle for operations like library scans. The [[`frontend/src/views/Scan/ScanPage.vue`](https://github.com/rommapp/romm/blob/main/frontend/src/views/Scan/ScanPage.vue)](https://github.com/rommapp/romm/blob/master/frontend/src/views/Scan/ScanPage.vue) demonstrates the pattern:

```ts
import socket from "@/services/socket";

function startScan() {
  socket.connect();

  socket.on("scan:update_stats", (stats) => {
    scanStore.updateProgress(stats);
  });

  socket.on("scan:log", (msg) => {
    scanStore.appendLog(msg);
  });

  socket.once("scan:stop", () => {
    socket.disconnect();
    scanStore.setComplete();
  });
}

```

The backend emits three primary event types during scans:

- **`scan:update_stats`**: Contains live counters for processed ROMs and platforms
- **`scan:log`**: Streams log messages as they are generated
- **`scan:stop`**: Signals scan completion, triggering client-side cleanup

### Connection Resilience

Socket.IO handles automatic reconnection with exponential backoff. The client can monitor connection health through built-in events:

```ts
socket.on("connect_error", (err) => {
  console.warn("WebSocket error:", err);
  // Trigger fallback UI or polling mode
});

```

All socket interactions are encapsulated in the default export from [`socket.ts`](https://github.com/rommapp/romm/blob/main/socket.ts), keeping the rest of the codebase agnostic to the underlying library implementation.

## Complete User Flow Example

A typical interaction combining both channels occurs when browsing the gallery and initiating a scan:

1. The **Gallery page** mounts and calls `fetchRoms()` via the Pinia store
2. The **Axios** client executes `GET /api/roms?platform_id=...` with automatic CSRF headers
3. The response updates the reactive store, triggering UI refresh
4. The user initiates a **library scan**, calling `socket.connect()`
5. The backend pushes progress via **Socket.IO** events while the REST API remains available for other operations
6. Upon receiving `scan:stop`, the client disconnects the socket and refreshes ROM data via REST

This separation ensures that long-running WebSocket operations do not block standard HTTP requests, maintaining UI responsiveness throughout the scanning process.

## Summary

- **Axios configuration**: Centralized in [`frontend/src/services/api/index.ts`](https://github.com/rommapp/romm/blob/main/frontend/src/services/api/index.ts) with baseURL `/api`, 120-second timeout, and automatic CSRF injection via `x-csrftoken` headers
- **Network tracking**: The `inflightRequests` Set monitors active HTTP calls to coordinate global loading states and backend health detection
- **Socket.IO setup**: Configured in [`frontend/src/services/socket.ts`](https://github.com/rommapp/romm/blob/main/frontend/src/services/socket.ts) with path `/ws/socket.io/` and manual connection management to control resource usage
- **Event architecture**: Real-time features use specific events (`scan:update_stats`, `scan:log`, `scan:stop`) to stream progress without polling overhead
- **Type safety**: Both channels leverage OpenAPI-generated TypeScript models located in `frontend/src/__generated__/models/`

## Frequently Asked Questions

### How does RomM handle authentication for API requests?

RomM injects CSRF tokens automatically through Axios request interceptors. The `x-csrftoken` header is populated from the `romm_csrftoken` cookie on every request, while the backend validates session state for protected endpoints. This dual-layer approach prevents cross-site request forgery while maintaining session continuity.

### What WebSocket events does RomM use for scan operations?

RomM emits three specific events during library scans: `scan:update_stats` transmits progress counters for processed files, `scan:log` streams real-time log output to the frontend, and `scan:stop` signals completion to trigger cleanup. These events are handled in the ScanPage component and stored in the scan Pinia store for reactive UI updates.

### How does the frontend track when API requests are in flight?

The Axios instance maintains an `inflightRequests` Set that tracks URLs of active requests. Request interceptors add URLs to the set, while response interceptors remove them. When the set empties, the system emits a `network-quiesced` event, allowing components to hide loading indicators and enabling optimistic UI updates.

### Why does RomM use both REST APIs and WebSockets instead of just one protocol?

RomM uses REST APIs for standard CRUD operations, configuration, and metadata retrieval because these are stateless, cacheable, and fit the request-response model. WebSockets handle real-time push notifications for long-running processes like library scans and log streaming because they eliminate polling overhead and provide immediate server-to-client updates without repeated HTTP requests.