# How the SpotifySaver Web UI Communicates with FastAPI for Real-Time Download Progress

> Discover how SpotifySaver's web UI connects with FastAPI using HTTP polling for real-time download progress updates. Learn about the JavaScript API and task status fetching.

- Repository: [Gabriel Baute/spotify-saver](https://github.com/gabrielbaute/spotify-saver)
- Tags: internals
- Published: 2026-03-02

---

**The SpotifySaver web UI communicates with the FastAPI backend using HTTP polling via a JavaScript API client that fetches task status every 2 seconds from an in-memory task store updated by background download callbacks.**

The gabrielbaute/spotify-saver repository implements a lightweight polling mechanism to provide real-time feedback during Spotify playlist downloads. Instead of WebSockets or Server-Sent Events, the web UI communicates with the FastAPI backend through periodic HTTP requests to track download progress, current tracks, and completion status. This architecture keeps the implementation simple while delivering near real-time updates to users.

## Architecture Overview

The system uses a stateless HTTP approach where the FastAPI backend maintains download state in an in-memory dictionary. When a user initiates a download, the backend spawns a background task and returns a UUID identifier. The JavaScript frontend polls a dedicated status endpoint every two seconds to retrieve updated progress information.

## Step-by-Step Communication Flow

### Initial API Client Setup

The connection begins in [`spotifysaver/ui/static/js/api-client.js`](https://github.com/gabrielbaute/spotify-saver/blob/main/spotifysaver/ui/static/js/api-client.js), which instantiates an `ApiClient` class pointing to the FastAPI base URL (`http(s)://<host>:8000/api/v1`). This wrapper handles all HTTP communication with the backend service, providing methods for initiating downloads and checking status.

### Initiating the Download Task

When users click the download button, `DownloadManager.startDownload()` collects form data and calls `apiClient.startDownload()`. This sends a **POST** request to `/api/v1/download` with parameters including the Spotify URL, output format, and bitrate. The endpoint in [`spotifysaver/api/routers/download.py`](https://github.com/gabrielbaute/spotify-saver/blob/main/spotifysaver/api/routers/download.py) (lines 33-84) validates the request, generates a UUID task ID, and launches an asynchronous background task before returning the ID to the client.

```javascript
const formData = {
  spotify_url: document.getElementById('spotify-url').value,
  output_dir: document.getElementById('output-dir').value,
  output_format: document.getElementById('format').value,
  bit_rate: document.getElementById('bitrate').value === 'best' ? 256 : parseInt(document.getElementById('bitrate').value),
  download_lyrics: document.getElementById('include-lyrics').checked,
  download_cover: true,
  generate_nfo: document.getElementById('create-nfo').checked,
};

const result = await apiClient.startDownload(formData);
// result.task_id stored for polling

```

### Background Task Processing with Progress Callbacks

The backend maintains state in an in-memory `tasks` dictionary defined in [`download.py`](https://github.com/gabrielbaute/spotify-saver/blob/main/download.py). As the download progresses, the `download_task` function invokes a `progress_callback` that updates the task object with current track information, completed track counts, and percentage calculations. This callback mutates the shared state that the status endpoint will read.

```python
async def download_task(task_id: str, request: DownloadRequest):
    task = tasks[task_id]
    task.status = "processing"

    def progress_callback(current, total, track_name):
        task.current_track = track_name
        task.completed_tracks = current - 1
        task.total_tracks = total
        task.progress = int((current / total) * 100) if total else 0

    await download_service.download_from_url(
        str(request.spotify_url), progress_callback=progress_callback
    )
    task.status = "completed"
    task.progress = 100

```

### Polling for Status Updates

After receiving the task ID, `DownloadManager.startProgressMonitoring()` in [`spotifysaver/ui/static/js/download-manager.js`](https://github.com/gabrielbaute/spotify-saver/blob/main/spotifysaver/ui/static/js/download-manager.js) initiates a 2-second polling loop. Each iteration calls `apiClient.getDownloadStatus(taskId)`, which executes a **GET** request to `/api/v1/download/{task_id}/status`. The endpoint retrieves the current state from the in-memory store and returns a `DownloadStatus` model containing `status`, `progress`, `current_track`, and `total_tracks` fields.

```javascript
async function poll(taskId) {
  const status = await apiClient.getDownloadStatus(taskId);
  if (status.status === 'processing') {
    uiManager.updateProgress(status.progress);
    uiManager.updateStatus(`Downloading… ${status.progress}%`);
    setTimeout(() => poll(taskId), 2000);
  } else if (status.status === 'completed') {
    uiManager.updateProgress(100);
    uiManager.updateStatus('Download completed');
  } else if (status.status === 'failed') {
    uiManager.updateStatus(`Error: ${status.error_message}`, 'error');
  }
}

```

### Rendering Progress in the UI

Upon each poll response, `handleDownloadProgress()` (lines 98-132 in [`download-manager.js`](https://github.com/gabrielbaute/spotify-saver/blob/main/download-manager.js)) updates the DOM elements. It adjusts the progress bar width, updates status text with the current track name, and modifies per-track icons based on the completion state. The loop continues until the status returns "completed" or "failed".

## CORS Configuration and Static File Serving

To enable cross-origin communication between the browser and API, the FastAPI application configures CORS middleware in [`spotifysaver/api/app.py`](https://github.com/gabrielbaute/spotify-saver/blob/main/spotifysaver/api/app.py) (lines 36-43) using origins defined in `APIConfig.ALLOWED_ORIGINS`. The same file mounts static file routes to serve the UI assets, ensuring seamless integration between the frontend and backend during local development and deployment.

## Summary

- The web UI uses **HTTP polling** rather than WebSockets to track download progress via the FastAPI backend
- An in-memory `tasks` dictionary stores real-time state updated via **progress callbacks** during background downloads
- The JavaScript `ApiClient` communicates with FastAPI endpoints every **2 seconds** to fetch current status
- The **POST** `/api/v1/download` endpoint initiates tasks while **GET** `/api/v1/download/{task_id}/status` provides progress updates
- CORS configuration in [`app.py`](https://github.com/gabrielbaute/spotify-saver/blob/main/app.py) enables secure cross-origin requests between the UI and API

## Frequently Asked Questions

### Does SpotifySaver use WebSockets for real-time updates?

No, the application implements a polling strategy using standard HTTP fetch requests. The `DownloadManager` class queries the status endpoint every 2 seconds, which provides near real-time feedback without the complexity of maintaining persistent WebSocket connections or managing connection state.

### How does the backend track download progress across multiple concurrent downloads?

The FastAPI backend stores each download task in an in-memory dictionary keyed by UUID in [`spotifysaver/api/routers/download.py`](https://github.com/gabrielbaute/spotify-saver/blob/main/spotifysaver/api/routers/download.py). The `progress_callback` function updates specific task entries with current track names and completion percentages, allowing the status endpoint to serve state for any active download independently without interference between tasks.

### What happens if the polling request fails or times out?

The polling loop in [`download-manager.js`](https://github.com/gabrielbaute/spotify-saver/blob/main/download-manager.js) handles network errors by continuing to retry on the next 2-second interval. If the backend returns a "failed" status or the task encounters an error, the UI displays the error message and terminates the polling loop to prevent unnecessary requests and conserve bandwidth.

### Can the polling interval be adjusted for faster updates?

Yes, developers can modify the polling frequency by changing the timeout value in the `startProgressMonitoring` method within [`spotifysaver/ui/static/js/download-manager.js`](https://github.com/gabrielbaute/spotify-saver/blob/main/spotifysaver/ui/static/js/download-manager.js). However, shorter intervals increase server load and API traffic, while longer intervals reduce the responsiveness of progress updates displayed to users.