Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

3 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🎡 Playlist & Recommendation Service API

A production-grade mini Spotify backend built with FastAPI β€” implementing classic System Design patterns: Strategy, Observer, Repository, and Cache-Aside, over a clean layered architecture.

FastAPI Python SQLAlchemy Redis


πŸ“Œ Overview

This project is a System Design case study built as a real, runnable FastAPI application. It demonstrates how to architect a scalable music streaming backend from scratch β€” covering playlist management, three-strategy recommendation engine, Redis caching, token-bucket rate limiting, and observer-based notifications.

What this project teaches:

  • How to apply Gang of Four design patterns in a real Python service
  • How Cache-Aside pattern reduces database load by ~10Γ—
  • How the Strategy pattern enables pluggable, swappable algorithms
  • How the Observer pattern decouples event producers from consumers
  • How the Repository pattern cleanly separates business logic from data access

✨ Features

Feature Description Pattern Used
🎡 Playlist CRUD Create, read, update, delete playlists; add/remove songs Repository Pattern
πŸ€– Recommendation Engine 3 strategies: Popularity, Content-Based, Collaborative Filtering Strategy Pattern
⚑ Redis Caching Cache-aside with 10-minute TTL; 10Γ— faster reads Cache-Aside Pattern
🚦 Rate Limiting Token Bucket β€” 100 req/min per user via X-User-Id header Middleware
πŸ”” Notifications Email + Push notifications on playlist events (simulated) Observer Pattern
🌐 Frontend UI Served static SPA at / for interactive API exploration FastAPI Static Files
πŸ§ͺ Test Suite Full pytest coverage for playlists, cache, rate limiter, recommendations pytest + httpx

πŸ—οΈ Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        Client                           β”‚
β”‚               (Browser / API / curl)                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                       β”‚ HTTP
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              Rate Limiter Middleware                     β”‚
β”‚         Token Bucket: 100 req/min per user              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                       β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  FastAPI Routes                         β”‚
β”‚        /playlists  /recommendations  /songs             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚                          β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Playlist Service β”‚   β”‚     Recommendation Engine      β”‚
β”‚  + EventManager    β”‚   β”‚  Strategy: popularity /        β”‚
β”‚    (Observer)      β”‚   β”‚  content_based / collaborative β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚                         β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   Cache Layer (Redis)                   β”‚
β”‚         CacheManager β€” Cache-Aside Pattern              β”‚
β”‚         TTL: 600s β”‚ fakeredis (zero-setup)              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                       β”‚ cache miss
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚               Repository Layer                          β”‚
β”‚       PlaylistRepository β”‚ SongRepository               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                       β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚               Database (SQLite + SQLAlchemy)            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

🧩 Design Patterns in Detail

🎯 Strategy Pattern β€” Recommendation Engine

The RecommendationEngine acts as a context that holds a pluggable RecommendationStrategy. The active algorithm can be switched at runtime via the ?strategy= query parameter β€” no code changes needed.

# Three concrete strategies, all implementing the same interface
STRATEGY_MAP = {
    "popularity":    PopularityBasedStrategy,    # top trending songs
    "content_based": ContentBasedStrategy,       # genre/artist match
    "collaborative": CollaborativeFilteringStrategy,  # Jaccard similarity
}

engine = RecommendationEngine(strategy_name="collaborative")
songs  = engine.get_recommendations(db, user_id="u1", n=10)

πŸ‘οΈ Observer Pattern β€” Notifications

The EventManager (Subject) maintains a registry of Observer implementations. When a playlist event fires, all registered observers are notified β€” fully decoupled from the service layer.

EventManager ──subscribe──► EmailNotifier  (logs simulated email)
             ──subscribe──► PushNotifier   (logs simulated push)

# Events: PLAYLIST_CREATED, SONG_ADDED, PLAYLIST_UPDATED
event_manager.notify(EventType.SONG_ADDED, {"playlist_id": "...", "song_id": "..."})

πŸ—„οΈ Repository Pattern β€” Data Access

All database queries are behind PlaylistRepository and SongRepository. The service layer never writes raw SQL β€” it calls typed methods like get_by_user_id() or get_top_by_popularity().

⚑ Cache-Aside Pattern β€” Redis

READ  β†’ check cache β†’ HIT: return cached data
                    β†’ MISS: query DB β†’ populate cache β†’ return data
WRITE β†’ update DB β†’ invalidate cache key
Metric Without Cache With Cache
Response Time ~50ms ~5ms
DB Load High Low
Scalability Medium High

🚦 Token Bucket Rate Limiter

Each user (X-User-Id header or IP) gets a bucket of 100 tokens that refills at 100 tokens/minute. Exceeding the limit returns HTTP 429 with a Retry-After header.


πŸ“ Project Structure

Playlist & Recommendation API/
β”‚
β”œβ”€β”€ app/
β”‚   β”œβ”€β”€ main.py                        # FastAPI app, startup wiring
β”‚   β”œβ”€β”€ config.py                      # All config constants (TTL, rate limits, DB URL)
β”‚   β”œβ”€β”€ database.py                    # SQLAlchemy engine, session factory, init_db()
β”‚   β”‚
β”‚   β”œβ”€β”€ models/
β”‚   β”‚   β”œβ”€β”€ playlist.py                # ORM models: PlaylistORM, SongORM, association table
β”‚   β”‚   └── response.py                # Pydantic APIResponse wrapper
β”‚   β”‚
β”‚   β”œβ”€β”€ repositories/
β”‚   β”‚   β”œβ”€β”€ base.py                    # Abstract BaseRepository
β”‚   β”‚   β”œβ”€β”€ playlist_repo.py           # PlaylistRepository (CRUD + user queries)
β”‚   β”‚   └── song_repo.py               # SongRepository (catalog, genre filter, seed data)
β”‚   β”‚
β”‚   β”œβ”€β”€ services/
β”‚   β”‚   β”œβ”€β”€ playlist_service.py        # Business logic for playlist operations
β”‚   β”‚   └── recommendation/
β”‚   β”‚       β”œβ”€β”€ base.py                # RecommendationStrategy (ABC)
β”‚   β”‚       β”œβ”€β”€ engine.py              # RecommendationEngine (Strategy context)
β”‚   β”‚       β”œβ”€β”€ popularity.py          # Sort songs by play count
β”‚   β”‚       β”œβ”€β”€ content_based.py       # Match user's genre/artist preferences
β”‚   β”‚       └── collaborative.py       # Jaccard-similarity user-based CF
β”‚   β”‚
β”‚   β”œβ”€β”€ cache/
β”‚   β”‚   └── redis_cache.py             # CacheManager (get/set/invalidate/flush)
β”‚   β”‚
β”‚   β”œβ”€β”€ middleware/
β”‚   β”‚   └── rate_limiter.py            # TokenBucket + RateLimiterMiddleware
β”‚   β”‚
β”‚   β”œβ”€β”€ notifications/
β”‚   β”‚   β”œβ”€β”€ observer.py                # EventType, Observer (ABC), EventManager
β”‚   β”‚   β”œβ”€β”€ email_notifier.py          # EmailNotifier (Observer impl.)
β”‚   β”‚   └── push_notifier.py           # PushNotifier (Observer impl.)
β”‚   β”‚
β”‚   └── routes/
β”‚       β”œβ”€β”€ playlist_routes.py         # /playlists endpoints
β”‚       └── recommendation_routes.py   # /recommendations + /songs endpoints
β”‚
β”œβ”€β”€ static/
β”‚   β”œβ”€β”€ index.html                     # Frontend SPA
β”‚   β”œβ”€β”€ styles.css                     # UI styles
β”‚   └── app.js                         # Frontend logic
β”‚
β”œβ”€β”€ tests/
β”‚   β”œβ”€β”€ test_playlists.py              # Playlist CRUD tests
β”‚   β”œβ”€β”€ test_recommendations.py        # Strategy & engine tests
β”‚   β”œβ”€β”€ test_cache.py                  # Cache hit/miss/invalidation tests
β”‚   └── test_rate_limiter.py           # Token bucket tests
β”‚
β”œβ”€β”€ requirements.txt
└── README.md

πŸš€ Quick Start

1. Clone the repository

git clone https://github.com/not-shivansh/Playlist-Recommendation-Service-API-FastAPI-Redis-System-Design-Project.git
cd "Playlist-Recommendation-Service-API-FastAPI-Redis-System-Design-Project"

2. Create a virtual environment

python -m venv venv
# Windows
venv\Scripts\activate
# macOS / Linux
source venv/bin/activate

3. Install dependencies

pip install -r requirements.txt

4. Run the server

uvicorn app.main:app --reload --port 8000

5. Explore

URL Description
http://localhost:8000 Frontend UI
http://localhost:8000/docs Swagger / Interactive API Docs
http://localhost:8000/redoc ReDoc API Reference
http://localhost:8000/health Health check

Note: The project uses fakeredis by default β€” no Redis server required. To use a real Redis instance, set USE_FAKE_REDIS = False and configure REDIS_URL in app/config.py.


πŸ“ API Reference

Playlists

Method Endpoint Description
POST /playlists Create a new playlist
GET /playlists List all playlists
GET /playlists/{id} Get a playlist by ID
PUT /playlists/{id} Update playlist name
DELETE /playlists/{id} Delete a playlist
POST /playlists/{id}/songs/{song_id} Add a song to a playlist
DELETE /playlists/{id}/songs/{song_id} Remove a song from a playlist

Create Playlist β€” Request Body:

{
  "name": "My Chill Mix",
  "user_id": "user_42"
}

Recommendations

Method Endpoint Description
GET /recommendations Get recommendations with chosen strategy
GET /recommendations/personalized Personalized content-based recommendations

Query Parameters for /recommendations:

Parameter Type Default Description
user_id string required User to generate recommendations for
strategy string popularity One of: popularity, content_based, collaborative
n int 10 Number of songs to return (1–50)

Example:

curl "http://localhost:8000/recommendations?user_id=alice&strategy=collaborative&n=5"

Songs

Method Endpoint Description
GET /songs Browse the full song catalog
GET /songs?genre=Rock Filter songs by genre

System

Method Endpoint Description
GET / Serves the frontend SPA
GET /health Returns {"status": "healthy"}

Unified API Response Format

All endpoints return a consistent envelope:

{
  "success": true,
  "message": "Recommendations (collaborative)",
  "data": [ ... ]
}

On errors:

{
  "success": false,
  "message": "Rate limit exceeded. Try again later.",
  "data": null
}

πŸ”’ Rate Limiting

Pass X-User-Id header to identify users. Without it, requests are bucketed by IP.

curl -H "X-User-Id: alice" http://localhost:8000/recommendations?user_id=alice

Response headers on every request:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 97

When the bucket is exhausted:

HTTP 429 Too Many Requests
Retry-After: 60

πŸ§ͺ Running Tests

pytest tests/ -v

Test coverage:

  • test_playlists.py β€” CRUD operations, song management
  • test_recommendations.py β€” All three strategies, strategy switching
  • test_cache.py β€” Cache hit/miss, TTL, invalidation, flush
  • test_rate_limiter.py β€” Token consumption and 429 enforcement

πŸ› οΈ Tech Stack

Layer Technology
Web Framework FastAPI 0.115 β€” async, OpenAPI-first
ASGI Server Uvicorn 0.30
ORM SQLAlchemy 2.0 β€” async-compatible, declarative models
Validation Pydantic v2 β€” strict schema validation
Database SQLite β€” zero-config, file-based
Cache fakeredis 2.24 β€” Redis-compatible, in-process (no infra needed)
Testing pytest 8.3 + httpx 0.27 (async test client)

βš™οΈ Configuration

All tunable parameters live in app/config.py:

USE_FAKE_REDIS       = True          # False β†’ connect to real Redis
REDIS_URL            = "redis://localhost:6379/0"
CACHE_TTL_SECONDS    = 600           # 10-minute cache TTL
RATE_LIMIT_CAPACITY  = 100           # token bucket size
RATE_LIMIT_WINDOW_SECONDS = 60       # refill window
DATABASE_URL         = "sqlite:///playlist_service.db"

πŸ—ΊοΈ Future Enhancements

  • JWT / OAuth2 authentication
  • Real Redis with persistence and Pub/Sub
  • WebSocket real-time notifications
  • ML-powered recommendation engine (matrix factorization)
  • Docker + Docker Compose setup
  • PostgreSQL migration guide
  • Deployment on AWS / GCP / Railway

Author

This project is open-source. Shivansh Thakur.


Built as a System Design project demonstrating scalable backend architecture patterns
Strategy Β· Observer Β· Repository Β· Cache-Aside Β· Token Bucket

About

A production-style backend inspired by Spotify supporting playlist CRUD and recommendation strategies. Built with FastAPI and Redis, it showcases cache-aside caching, token bucket rate limiting, and design patterns like SOLID, Strategy, and Observer, focusing on scalability, performance, and clean architecture.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages