How the SpotifySaver Web UI Communicates with FastAPI for Real-Time Download Progress
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, 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 (lines 33-84) validates the request, generates a UUID task ID, and launches an asynchronous background task before returning the ID to the client.
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. 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.
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 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.
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) 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 (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
tasksdictionary stores real-time state updated via progress callbacks during background downloads - The JavaScript
ApiClientcommunicates with FastAPI endpoints every 2 seconds to fetch current status - The POST
/api/v1/downloadendpoint initiates tasks while GET/api/v1/download/{task_id}/statusprovides progress updates - CORS configuration in
app.pyenables 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. 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 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. However, shorter intervals increase server load and API traffic, while longer intervals reduce the responsiveness of progress updates displayed to users.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →