Music for the next fave.
A home for indie artists to upload tracks, share releases, and help listeners discover new music — zero gatekeepers.
- Features
- Tech Stack
- Architecture
- Project Structure
- Getting Started
- Available Scripts
- Environment Variables
- Frontend Overview
- API Integration
- Authentication
- Music Player
- Playlists
- Deployment
- Contributing
- License
- Upload audio files with metadata (title, artist, genre, mood, tags)
- Stream audio with HTTP range-request support
- Download tracks as file attachments
- Rich metadata: BPM, musical key, lyrics, cover art, waveform data
- Edit and delete tracks with ownership enforcement
- Persistent bottom player bar with play/pause, seek, skip, and volume controls
- Full-screen PlayScreen with album art, lyrics display, and sharing options
- Queue management — play next, previous, and queue insertion
- Auto-play configurable per track selection
- Like, download, and share tracks directly from the player
- Full CRUD: create, rename, delete playlists
- Add and remove tracks from playlists
- Drag-and-drop reordering (planned)
- Collaborative sharing — share playlists via link with view/edit permissions
- Shared playlist view for recipients (no login required with link)
- Search and filter your playlists
- Dedicated artist pages with bio, stats, and social links
- Follower count, monthly listener metrics
- Track listing with play controls
- Album discography display
- Support/patronage dialog
- Profile customization (avatar, header image, location, website)
- Browse all tracks with sort options: Latest, Most Played, Most Liked
- Genre filtering with horizontal pill selector
- Paginated results (8 tracks per page)
- Search tracks and artists via sidebar or discover page
- Full-text search backed by Django backend
- Follow/unfollow artists
- Like/unlike tracks
- Email verification and password reset flows
- JWT authentication with automatic token refresh
- Per-track statistics: daily plays, unique listeners, average listen duration, completion rate
- Per-user statistics: total tracks, plays, likes, downloads, followers, monthly listeners
- Play tracking with duration and completion metrics
- "Dark Future Studio" theme — deep charcoal backgrounds, emerald green accents, glassmorphism surfaces
- Responsive design — mobile bottom navigation, desktop sidebar layout
- Smooth animations powered by Framer Motion
- shadcn/ui component library with Radix UI primitives
- Noise textures, glow effects, and gradient backgrounds
| Category | Technology |
|---|---|
| Core | React 19, TypeScript, Vite 7 |
| Routing | Wouter — minimal client-side router |
| UI Components | shadcn/ui (New York style), Radix UI primitives |
| Styling | Tailwind CSS v4, CSS variables design tokens |
| Animation | Framer Motion |
| Data Fetching | TanStack React Query |
| Forms | React Hook Form + Zod validation |
| Icons | Lucide React, React Icons |
| Rich Text | Tiptap (lyrics editor) |
| Charts | Recharts |
| Carousel | Embla Carousel |
| Toast | [Sonner](https://sonner.emilkowal ski.com/) |
| Category | Technology |
|---|---|
| Local Dev Server | Express 5 |
| Production API | Django REST Framework (hosted externally) |
| Database | PostgreSQL |
| ORM (schema) | Drizzle ORM |
| Auth | JWT via Django SimpleJWT |
| Real-time | WebSockets |
| Build | esbuild (server bundling), Vite (client bundling) |
┌─────────────────────────────────────────────────────────────────┐
│ Browser │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ React SPA (Vite 7) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌───────────────────────┐ │ │
│ │ │AuthContext│ │PlayerCtx │ │PlaylistContext │ │ │
│ │ └─────┬────┘ └────┬─────┘ └──────────┬────────────┘ │ │
│ │ │ │ │ │ │
│ │ ┌─────┴────────────┴───────────────────┴────────────┐ │ │
│ │ │ Pages (Wouter Router) │ │ │
│ │ │ Home | Discover | Upload | Artist | Playlists │ │ │
│ │ │ PlaylistDetail | SharedPlaylist | VerifyEmail │ │ │
│ │ │ ResetPassword | 404 │ │ │
│ │ └───────────────────────┬───────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌───────────────────────▼───────────────────────────┐ │ │
│ │ │ API Client (apiRequestJson) │ │ │
│ │ │ ~ snake_case ↔ camelCase auto-transformation ~ │ │ │
│ │ │ ~ JWT Bearer token injection ~ │ │ │
│ │ │ ~ Automatic 401 → token refresh retry ~ │ │ │
│ │ └───────────────────────┬───────────────────────────┘ │ │
│ └──────────────────────────┼──────────────────────────────┘ │
└─────────────────────────────┼────────────────────────────────┘
│ HTTPS
┌─────────────────────────────▼────────────────────────────────┐
│ Django REST Framework API │
│ Hosted at https://kofficobbin.pythonanywhere.com │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ Users │ │ Tracks │ │ Playlists│ │ Social │ │
│ │ Auth │ │ Albums │ │ Shares │ │ Likes │ │
│ │ Profiles│ │ Stream │ │ │ │ Follows │ │
│ └──────────┘ └──────────┘ └──────────┘ │ Comments │ │
│ └──────────────┘ │
│ ┌──────────┐ ┌──────────┐ │
│ │Analytics │ │ Search │ PostgreSQL Database │
│ │ Stats │ │ Index │ │
│ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────────┘
-
Separated Backend: The Django REST API runs independently. The Express server in this repo only serves the Vite-built frontend in production (or proxies Vite in development). All actual API logic lives in the Django backend.
-
Case Transformation Layer: The Django API uses
snake_casebut the React frontend usescamelCase. TheapiRequestJsonutility automatically transforms all request bodies (camelCase→snake_case) and response bodies (snake_case→camelCase). -
JWT Auto-Refresh: If any API call returns a 401, the client automatically attempts a token refresh before retrying. This makes authentication transparent to the user.
-
Context-Driven State: Auth, Player, and Playlist state are managed via React Context providers, avoiding prop drilling for cross-cutting concerns.
MuseWave/
├── client/ # React frontend
│ ├── src/
│ │ ├── components/
│ │ │ ├── ui/ # shadcn/ui primitives
│ │ │ │ ├── accordion.tsx
│ │ │ │ ├── button.tsx
│ │ │ │ ├── card.tsx
│ │ │ │ ├── dialog.tsx
│ │ │ │ ├── dropdown-menu.tsx
│ │ │ │ ├── input.tsx
│ │ │ │ ├── toast.tsx
│ │ │ │ └── ... (60+ components)
│ │ │ ├── playlists/
│ │ │ │ ├── PlaylistCard.tsx
│ │ │ │ ├── CreatePlaylistModal.tsx
│ │ │ │ ├── RenamePlaylistModal.tsx
│ │ │ │ ├── SharePlaylistModal.tsx
│ │ │ │ └── TrackActionsMenu.tsx
│ │ │ ├── BottomNav.tsx # Mobile bottom navigation
│ │ │ ├── LoginModal.tsx # Login/signup dialog
│ │ │ ├── LoginDialog.tsx
│ │ │ ├── PlayerBar.tsx # Persistent audio player bar
│ │ │ ├── PlayScreen.tsx # Full-screen player view
│ │ │ ├── TrackCard.tsx # Shared track card component
│ │ │ ├── LyricsEditor.tsx # Tiptap-based lyrics editor
│ │ │ └── album-create.tsx # Album creation component
│ │ ├── contexts/
│ │ │ ├── auth-context.tsx # JWT authentication state
│ │ │ ├── player-context.tsx # Audio player state & queue
│ │ │ └── playlist-context.tsx # Playlist CRUD state
│ │ ├── hooks/
│ │ │ ├── use-genres.ts # Genre list fetching
│ │ │ ├── use-mobile.tsx # Mobile detection hook
│ │ │ └── use-toast.ts # Toast notification hook
│ │ ├── lib/
│ │ │ ├── apiConfig.ts # API endpoints config
│ │ │ ├── queryClient.ts # API client with JWT & case transform
│ │ │ ├── caseTransform.ts # snake_case ↔ camelCase utilities
│ │ │ └── utils.ts # Shared utilities
│ │ ├── pages/
│ │ │ ├── home.tsx # Landing page (hero, tracks, trending)
│ │ │ ├── discover.tsx # Browse tracks with filters
│ │ │ ├── upload.tsx # Track upload form
│ │ │ ├── artist.tsx # Artist profile page
│ │ │ ├── playlists.tsx # User's playlist list
│ │ │ ├── playlist-detail.tsx # Single playlist view
│ │ │ ├── shared-playlist.tsx # Shared/shared playlist view
│ │ │ ├── verify-email.tsx # Email verification handler
│ │ │ ├── reset-password.tsx # Password reset form
│ │ │ └── not-found.tsx # 404 page
│ │ ├── App.tsx # Root component & router
│ │ ├── main.tsx # Entry point
│ │ └── index.css # Global styles & design tokens
│ └── index.html # HTML template
├── server/ # Express dev server
│ ├── index.ts # Server entry (middleware + routes)
│ ├── routes.ts # API routes (delegates to Django)
│ ├── static.ts # Production static file serving
│ └── vite.ts # Vite dev middleware setup
├── shared/ # Shared between client & server
│ └── schema.ts # Zod schemas (User, Track, Album, Playlist, etc.)
├── script/
│ └── build.ts # Production build script (esbuild + Vite)
├── public/ # Static assets
│ ├── assets/
│ │ └── homepage.png # Homepage screenshot
│ └── index.html # Built HTML template
├── dist/ # Build output
├── .env.development # Development environment
├── .env.production # Production environment
├── drizzle.config.ts # Drizzle ORM configuration
├── postcss.config.js # PostCSS config
├── tsconfig.json # TypeScript configuration
├── vite.config.ts # Vite configuration
├── components.json # shadcn/ui configuration
└── package.json # Dependencies & scripts
- Node.js >= 20.x
- npm or yarn
- A running instance of the Django REST backend (or access to the hosted API)
# Clone the repository
git clone https://github.com/your-org/musewave.git
cd musewave
# Install dependencies
npm installCreate a .env.local file in the project root (or modify the existing .env.development):
VITE_API_URL=https://your-django-backend.comThe default points to https://kofficobbin.pythonanywhere.com — the hosted Django API.
# Start both the Vite dev server and Express API server
npm run dev
# Or run them separately:
npm run dev:client # Vite dev server only (port 5000)
npm run dev # Full stack with ExpressThe app will be available at http://localhost:5000.
# Build client + bundle server
npm run build
# Start production server
npm start| Script | Command | Description |
|---|---|---|
dev |
cross-env NODE_ENV=development tsx server/index.ts |
Start full dev server (Express + Vite middleware) |
dev:client |
vite dev --port 5000 |
Start Vite dev server only (standalone frontend) |
build |
tsx script/build.ts |
Build client (Vite) + bundle server (esbuild) into dist/ |
start |
cross-env NODE_ENV=production node dist/index.cjs |
Start production server serving built assets |
check |
tsc |
Run TypeScript type checking (no emit) |
db:push |
drizzle-kit push |
Push Drizzle schema to PostgreSQL database |
| Variable | Required | Default | Description |
|---|---|---|---|
VITE_API_URL |
Yes | — | Base URL for the Django REST API backend |
DATABASE_URL |
For DB ops | — | PostgreSQL connection string (for Drizzle) |
PORT |
No | 5000 |
Port for the Express server |
NODE_ENV |
No | development |
Environment mode (development / production) |
| Route | Page Component | Description |
|---|---|---|
/ |
Home |
Landing page with hero, latest tracks, trending artists |
/discover |
Discover |
Browse all tracks with sorting, filtering, pagination |
/upload |
Upload |
Track upload form with metadata |
/artist/:slug |
Artist |
Artist profile page with stats and tracks |
/playlists |
Playlists |
User's playlist collection |
/playlists/:id |
PlaylistDetail |
Single playlist with track listing |
/playlists/link/:token |
SharedPlaylist |
Shared playlist view (public link) |
/verify-email/:uidb64/:token |
VerifyEmail |
Email verification handler |
/reset-password/:uid/:token |
ResetPassword |
Password reset form |
* |
NotFound |
404 page |
Three React Context providers wrap the application:
-
AuthContext (
auth-context.tsx)- Manages user authentication state
- Provides
login(),logout(),user,isAuthenticated - Persists JWT tokens in
localStorage - Loads user profile from the API on app mount
-
PlayerContext (
player-context.tsx)- Manages the active track, playback state, and queue
- Provides
setActive(),setQueue(),insertNext(),playNext(),playPrev() - Supports playlists queueing with next/previous navigation
-
PlaylistContext (
playlist-context.tsx)- Manages user playlists and the currently viewed playlist
- Provides CRUD operations:
fetchPlaylists(),createPlaylist(),deletePlaylist(),renamePlaylist() - Track management:
addSongToPlaylist(),removeSongFromPlaylist() - Sharing:
sharePlaylistWithUser(),fetchPlaylistByLink()
The app uses a comprehensive set of shadcn/ui components built on Radix UI primitives:
- Layout: Sidebar, Sheet, Resizable panels, Scroll area
- Input: Input, Textarea, Select, Checkbox, Radio group, Switch, Input OTP
- Feedback: Toast (Sonner), Alert dialog, Dialog, Progress, Skeleton, Spinner
- Navigation: Tabs, Navigation menu, Menubar, Breadcrumb, Pagination
- Data Display: Table, Card, Avatar, Badge, Separator, Tooltip
- Overlays: Popover, Dropdown menu, Context menu, Hover card, Command (cmd+k)
- Media: Carousel, Aspect ratio
- PlayerBar — Fixed-bottom audio player with play/pause, seek bar, track info, volume, and full-screen toggle
- PlayScreen — Full-screen immersive player with album art, seekable waveform/progress, lyrics display, like/download/share
- TrackCard — Reusable card used across Home, Discover, and Artist pages with play, menu, and add-to-playlist actions
- BottomNav — Mobile bottom navigation bar (Home, Discover, Upload, Account)
- LoginModal — Authentication dialog with login/signup forms
- LyricsEditor — Tiptap-based rich text editor for track lyrics
The API client is built in client/src/lib/queryClient.ts with these features:
- Automatic case conversion: Request bodies are converted from
camelCase→snake_casebefore sending. Response bodies are converted fromsnake_case→camelCaseon receipt. - JWT token injection: The
accessTokenfromlocalStorageis automatically attached as aBearertoken in theAuthorizationheader. - Automatic 401 retry: If a request returns 401, the client attempts a silent token refresh using the stored
refreshToken. If successful, it retries the original request. If the refresh fails, all tokens are cleared. - Error normalization: Server errors are parsed for meaningful messages (
error,detail,messagefields) and thrown as typed errors withstatuscodes.
All endpoints documented in the internal BACKEND_API_DOCUMENTATION.md. The API config is centralized in client/src/lib/apiConfig.ts:
| Category | Endpoints |
|---|---|
| Auth | POST /api/users/login, POST /api/users/logout, POST /api/users/refresh |
| Users | GET /api/users/:id, PATCH /api/users/:id, GET /api/users/username/:username |
| Tracks | GET /api/tracks, POST /api/tracks, GET /api/tracks/:id/stream/, GET /api/tracks/:id/download/ |
| Albums | POST /api/albums, GET /api/albums/:id, PATCH /api/albums/:id/update |
| Playlists | GET /api/playlists, POST /api/playlists, POST /api/playlists/:id/add-track |
| Social | POST /api/tracks/:id/like, POST /api/users/:id/follow |
| Search | GET /api/search?q=... |
| Analytics | GET /api/users/:id/stats, GET /api/tracks/:id/stats |
MuseWave uses JWT (JSON Web Tokens) for authentication via the Django REST backend.
- Login: User submits email/username + password → backend returns
access+refreshtokens + user data - Token Storage: Tokens and
userIdare stored inlocalStorage - Authenticated Requests: The
apiRequestJsonfunction injects theaccessTokenas aBearertoken in every request - Token Refresh: When a request returns
401, the client automatically calls/api/users/refreshwith the storedrefreshTokento obtain new tokens - Session Restoration: On page reload, if tokens exist in
localStorage, the auth context fetches the user profile to restore the session - Logout: Clears tokens from
localStorageand calls the backend logout endpoint
- Login: Via
LoginModalcomponent (dialog overlay on any page) - Sign Up: Via
LoginModal(tab-based — login/register in the same dialog) - Email Verification:
GET /api/users/verify-email/:uidb64/:token/— handled byVerifyEmailpage - Password Reset:
POST /api/users/password/reset→ email →POST /api/users/password/reset/confirm— handled byResetPasswordpage
The persistent bottom player bar is always visible when a track is selected:
- Track info: Album art thumbnail, title, artist
- Controls: Play/pause, skip forward, skip backward
- Seek bar: Draggable progress bar with time display
- Volume: Slider control
- Actions: Like (heart), download, share, support artist
- Expand: Opens full-screen PlayScreen
An immersive full-screen player dialog:
- Large album art with gradient background
- Seek bar with current/total time
- Playback controls: Play/pause, skip, rewind 10s, forward 10s
- Lyrics display: Scrollable, sanitized HTML lyrics via DOMPurify
- Volume slider with mute toggle
- Actions: Like, download, share, view artist page
- Keyboard shortcuts: Space (play/pause), arrows (seek)
The PlayerContext manages a track queue:
setQueue(tracks, startIndex)— Load a list of tracks starting at a given indexinsertNext(track)— Insert a track to play after the current oneplayNext()/playPrev()— Navigate the queuehasNext/hasPrev— Computed flags for UI state
The playlist system supports full CRUD with collaborative sharing:
- Create: Name + optional description via
CreatePlaylistModal - Read: List view (
/playlists) and detail view (/playlists/:id) with track listing - Update: Rename and change description via
RenamePlaylistModal - Delete: With confirmation dialog
- Add Tracks: From any
TrackCardvia theAddToPlaylistButtondropdown - Remove Tracks: From playlist detail view
- Share: Share with specific users (view/edit permissions) or generate a shareable link
- User shares: Grant
vieworeditpermissions to specific users - Link sharing: Generate a public token link for anyone to view
- Shared playlists: Accessible at
/playlists/link/:token— no login required - Shared with me: View playlists others have shared with you
The build script (script/build.ts) uses a two-step process:
- Client: Built with Vite → outputs to
dist/public/ - Server: Bundled with esbuild → outputs
dist/index.cjs
The bundled server includes only an allow-list of dependencies (express, drizzle-orm, passport, etc.) while externalizing the rest.
The app is designed to be deployed anywhere Node.js runs:
# Build
npm run build
# Start
NODE_ENV=production node dist/index.cjsThe Express server serves the static files from dist/public/ in production mode. For the Django backend, update VITE_API_URL in your environment variables.
The project includes Firebase hosting configuration (.firebaserc, firebase.json) for deploying the static frontend to Firebase Hosting.
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Commit your changes:
git commit -am 'Add my feature' - Push to the branch:
git push origin feature/my-feature - Open a Pull Request
- Follow existing TypeScript patterns and naming conventions
- Use Zod schemas from
shared/schema.tsfor type definitions - Keep components focused with single responsibility
- Use the existing Context providers for cross-cutting state
- Add tests for new features
- Ensure mobile responsiveness for UI changes
This project is licensed under the MIT License — see the LICENSE file for details.
- Built with shadcn/ui components
- Audio handling via Django REST Framework
- Deployed on PythonAnywhere
- Icons by Lucide and React Icons
- Fonts: Inter + Space Grotesk
MuseWave — music for the next fave.
No gatekeepers. Just music.
