Music streaming app with real-time play tracking, lyrics sharing, and mood-based home dashboard.
| Search | Library | Profile | Sign In |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
| Mini Player | Lyrics | Share Link |
|---|---|---|
![]() |
![]() |
![]() |
| Home | Player |
|---|---|
![]() |
![]() |
backend/
├── config.py # Environment variables, constants, R2 client
├── middleware.py # CORS + security headers + X-Request-ID (raw ASGI)
├── helpers.py # Response format, rate limiting, caching, validation, serialization, auth
├── crypto.py # Password hashing: argon2id (new) + bcrypt (legacy)
├── main.py # App bootstrap, exception handlers, router includes (local routes gated in prod)
├── db.py # Async SQLAlchemy engine + session factory
├── models.py # SQLAlchemy ORM models
├── schemas.py # Pydantic request/response models + OpenAPI error schemas
├── migrate.py # Idempotent SQL migration (no Alembic)
├── backfill_genre.py # Song genre enrichment via MusicBrainz + Wikipedia
├── import_songs.py # Catalog import: Genius lyrics + YouTube download + MusicBrainz genre
├── repositories/ # Database operations per entity
├── services/ # Business logic per entity
└── routes/ # API endpoints per entity
Layer flow: routes/ → services/ → repositories/ → db.py + models.py
web/muzix/
├── app/ # Expo Router file-based routes
│ ├── (tabs)/ # Main tab screens (home, search, library, profile)
│ ├── share/ # Public share link resolution
│ ├── _layout.tsx # Root layout: auth guard, offline banner, queue panel, keyboard shortcuts, Sentry
│ └── login.tsx, register.tsx
├── components/ # Reusable UI components
│ ├── NowPlaying.tsx # Full player view with lyrics sharing
│ ├── MiniPlayer.tsx # Persistent mini player bar
│ ├── QueuePanel.tsx # Queue management modal (reorder, remove, clear)
│ ├── LyricsPanel.tsx, LyricsImageGenerator.tsx
│ ├── ErrorBoundary.tsx # Sentry-wrapped error boundary
│ └── EmptyStates.tsx, Skeleton.tsx
├── hooks/ # Custom hooks
│ ├── useSharing.ts # Unified content sharing (API + native/web share)
│ ├── useKeyboardShortcuts.ts # Web keyboard controls (Space, arrows, N/P, L, Q, Esc)
│ ├── useHaptics.ts # Haptic feedback (native only, web no-op)
│ ├── useLyricsSharing.ts # Lyrics image generation via view-shot
│ └── useConnectivity.ts # Online/offline detection
├── store/ # Zustand state
│ ├── playerStore.ts # Player, queue, likes state with zustand persist
│ ├── authStore.ts # Auth token + user state
│ └── storage.ts # Cross-platform storage adapter (MMKV native / localStorage web)
├── services/
│ ├── api.ts # API client with retry, timeout, dedup, Sentry error reporting
│ ├── cache.ts # ETag-based API response cache (in-memory + localStorage)
│ ├── metrics.ts # Sentry metrics (API latency, queue depth, track plays)
│ ├── playTimeTracker.ts # Persistent play time accumulator with delta-flush
│ ├── offlineQueue.ts # Offline request queue with retry
│ └── auth.ts # Auth API helpers
└── lib/ # Colors, spacing, utilities, responsive breakpoints
Example of a single API visualized — one endpoint's request → response path.
Generate and view the interactive API flow from backend/:
npx api-understanding scan # → writes analysis.json
npx api-understanding dashboard analysis.json # → interactive dashboardSee algorithms/ for documented runtime logic: ALS recommendation engine, Fisher-Yates shuffle, play-time tracking, analytics scoring, caching strategies, and more — 16 algorithms with actual code references, constants, and input/output specs.
What's newest: the ALS model retraining now runs in the app lifespan as a background task (fit once on startup; subsequent requests serve the cached factor matrices).
- Content sharing: Generate share links for songs, albums, artists, playlists, lyrics. 30-day token expiry. Web Share API / native share sheet / clipboard fallback.
- Lyrics sharing: Select up to 5 lyrics lines, share as image (16:9 PNG) or plain text. Synced scrolling with LRC support.
- Home screen dashboard: 2x2 smart grid with current time, live weather (geolocation + wttr.in), mood derived from recently played song genres.
- Mood detection: Analyzes genre of your recent plays and displays a mood label + icon (Energetic, Calm, Confident, etc.).
- Genre enrichment: Song genre metadata fetched from MusicBrainz + Wikipedia, stored per-track in the database.
- MMKV storage: ~30x faster than AsyncStorage on native. Zustand persist + offline queue + play time tracker all use MMKV. Web falls back to localStorage.
- Audio playback: Uses
expo-audiofor cross-platform playback; downloads audio to cache viaexpo-file-system. Falls back gracefully when native TrackPlayer module is unavailable. - Play time tracking: Persistent per-song accumulator flushes deltas to
POST /telemetry/durationevery 30s. Survives app backgrounding and restarts. - Queue management: Slide-up panel with reorder (up/down arrows), remove, clear all.
- Keyboard shortcuts (web): Space=play/pause, arrows=next/prev, N/P=next/prev, L=like, Q=queue, Esc=close.
- Haptic feedback: Light/medium/success/error on native (no-op on web).
- Offline banner: Persistent top banner when disconnected.
- Pull-to-refresh: All detail screens (album, artist, playlist, profile).
- Responsive layout: Desktop sidebar, tablet split-view, mobile bottom tabs. Orientation-aware.
- Sentry: Error reporting + performance tracing (20% sample in production).
Sentry.wrap()on root layout,ErrorBoundarycatches component crashes, API layer reports errors with request IDs. - Sentry Metrics:
api_response_time,api_error,queue_depth,track_playtracked viaservices/metrics.ts. - Logfire: Request-level tracing with
logfire.instrument_fastapi(). Trace context stripped bySecurityMiddlewareto prevent cross-service contamination. - X-Request-ID: Every response includes a UUID. Accepts client-sent IDs for end-to-end correlation. Set as Sentry tag for debugging.
- Structured errors:
ApiErrorclass withErrorKind,ErrorSeverity, andretryablefields. Auth errors auto-redirect to login.
- Password hashing: argon2id (new registrations) with bcrypt fallback (existing users)
- JWT signing: HS384 (SHA-384 HMAC) with 24-hour expiry + 30-day refresh tokens
- CORS: Configurable allowed origins via
CORS_ORIGINSenv var - Rate limiting: Per-IP + per-path sliding window; 10 shares/min per user
- Security headers: X-Content-Type-Options, X-Frame-Options, HSTS, CSP, Referrer-Policy, Permissions-Policy
- Input validation: Pydantic models with email/password complexity rules
- IDOR protection: Playlist ownership checks on all mutation endpoints
- OpenAPI docs:
/docs(Swagger UI),/redoc(ReDoc), and/openapi.jsonare enabled with a fully typed spec — Pydantic request/response schemas, bearer-auth security scheme, and per-endpoint error responses for every route. - Path traversal blocked:
/thumbnails/{filename}rejects/,..,\, null bytes - Telemetry capped:
POST /telemetry/eventslimited to 50 events per request - Bearer auth documented: JWT-protected endpoints are declared with a
bearerAuthsecurity scheme in the generated OpenAPI spec - Dev routes gated:
/local/*routes and static file mounts only available whenENV != production
cd backend
cp .env.example .env # fill in values
uv sync
uv run python migrate.py # create tables
uv run uvicorn main:app --reload --host 0.0.0.0 --port 8000cd web/muzix
cp .env.example .env.local # set EXPO_PUBLIC_API_URL
pnpm install
pnpm dev| Var | Purpose |
|---|---|
DATABASE_URL |
PostgreSQL connection string (asyncpg) |
JWT_SECRET |
Secret key for JWT token signing (min 32 characters) |
R2_ACCOUNT_ID |
Cloudflare account ID |
R2_ACCESS_KEY_ID |
R2 API token access key |
R2_SECRET_ACCESS_KEY |
R2 API token secret |
R2_BUCKET |
Private bucket name (e.g. muzix-audio) |
R2_PUBLIC_URL |
Optional custom S3 endpoint |
CORS_ORIGINS |
Comma-separated frontend origins |
ENV |
Set to production to gate dev-only routes |
LOGFIRE_TOKEN |
Pydemon Logfire token for tracing |
UPSTASH_REDIS_REST_URL / UPSTASH_REDIS_REST_TOKEN |
Upstash Redis credentials for distributed rate limiting and catalog cache (optional; falls back to in-memory) |
| Var | Purpose |
|---|---|
EXPO_PUBLIC_API_URL |
Base URL of the FastAPI backend |
The migration (uv run python migrate.py) creates:
| Table | Description |
|---|---|
songs |
Track metadata with genre, full-text search (tsvector) |
albums |
Album metadata with FTS |
artists |
Artist metadata with FTS |
playlists |
User playlists (with M2M playlist_songs) |
users |
Auth accounts (email + argon2id hash) |
refresh_tokens |
JWT refresh tokens with rotation + revocation tracking (is_revoked BOOLEAN) |
listening_events |
Per-play telemetry |
user_sessions |
Session engagement metrics |
song_durations |
Persistent per-user listening time accumulator (unique on user_id + song_id) |
user_likes |
User song likes (unique constraint) |
shares |
Share links with 30-day expiry, per-content metadata |
All endpoints return standardized JSON:
{
"status": "success" | "failed" | "exception",
"data": {},
"message": "...",
"meta": { "pagination": { "total": 65, "limit": 100, ... } }
}| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /health |
No | Health check |
| GET | /songs |
No | List songs (paginated, brief, includes genre) |
| GET | /songs/{id} |
No | Get song by ID (full, includes genre) |
| GET | /albums |
No | List albums |
| GET | /albums/{id} |
No | Get album by ID |
| GET | /artists |
No | List artists |
| GET | /artists/{id} |
No | Get artist by ID |
| GET | /playlists |
Yes | List user playlists |
| POST | /playlists |
Yes | Create playlist |
| PUT | /playlists/{id} |
Yes | Update playlist |
| DELETE | /playlists/{id} |
Yes | Delete playlist |
| POST | /playlists/{id}/songs/{songId} |
Yes | Add song to playlist |
| DELETE | /playlists/{id}/songs/{songId} |
Yes | Remove song from playlist |
| GET | /likes |
Yes | Get user's liked songs |
| POST | /likes/{songId} |
Yes | Like a song |
| DELETE | /likes/{songId} |
Yes | Unlike a song |
| GET | /search?q= |
No | Full-text search (songs, albums, artists) |
| GET | /stream/{id} |
No | Get 1-hour presigned R2 URL |
| GET | /thumbnails/{id}.jpg |
No | Get song/album thumbnail |
| POST | /auth/register |
No | Create account |
| POST | /auth/login |
No | Get JWT + refresh token |
| POST | /auth/refresh |
No | Refresh JWT token |
| GET | /auth/me |
Yes | Current user profile |
| POST | /telemetry/events |
Yes | Batch insert listening events (max 50) |
| POST | /telemetry/duration |
Yes | Record accumulated play time for a song |
| POST | /telemetry/session/start |
Yes | Start session |
| POST | /telemetry/session/end |
Yes | End session |
| POST | /api/share/generate |
Yes | Generate share link (10/min) |
| GET | /api/share/{token} |
No | Resolve share link (public) |
| GET | /analytics/user/top-songs |
Yes | User's top songs |
| GET | /analytics/user/stats |
Yes | User listening stats |
| GET | /analytics/user/recent-activity |
Yes | Recent listening activity |
- Async everything: All database and R2 operations are async (boto3 calls wrapped in
asyncio.to_thread) - ETag caching: List endpoints return
ETag+Cache-Controlheaders; 304 onIf-None-Match - Distributed Redis cache: When Upstash credentials are set, rate limiting and catalog caching are distributed via Redis with automatic stale-key cleanup (falls back to in-memory)
- MMKV storage: ~30x faster than AsyncStorage on native for key-value operations
- Rate limiting: Sliding window per IP + path with automatic stale key cleanup
- Brief serialization: List responses omit
lyricsandr2_object_key(~3KB/song savings) - Local file caching: 60s TTL cache for local asset reads
- FTS indexes: GIN-indexed tsvector columns on songs, albums, artists
- Request deduplication: In-flight Map prevents duplicate concurrent requests to the same endpoint
Backend is deployed to FastAPI Cloud:
cd backend
uv run fastapi cloud deploy
Designed and developed by Akshat Kotpalliwar
Copyright © 2026 Akshat Kotpalliwar. All rights reserved. Licensed under the AGPL-3.0.











