Go to file
Brian Fertig 347cf97da7 feat: add Open Graph and Twitter Card meta tags for public pages
- Introduce appBaseUrl config option for absolute URLs in social cards
- Generate og metadata for song and playlist pages with title, description, image, and URL
- Include partial layout support for rendering OG/Twitter meta tags conditionally
2026-04-12 10:43:19 -06:00
db feat: add user profiles with slugs and enhance now-playing UI 2026-04-11 13:46:12 -06:00
public refactor: rename admin routes and views to mymusic namespace 2026-04-12 10:29:00 -06:00
src feat: add Open Graph and Twitter Card meta tags for public pages 2026-04-12 10:43:19 -06:00
.env.example feat: add configurable site name via SITE_NAME env var 2026-04-11 08:34:11 -06:00
.gitignore Adding env and gitignore 2026-04-09 18:46:37 -06:00
PWA.md feat: implement three-tier visibility system for songs and playlists 2026-04-09 22:40:18 -06:00
README.md Added Readme 2026-04-09 18:45:17 -06:00
package-lock.json feat: add user avatars with cropping and image lightbox 2026-04-11 10:08:05 -06:00
package.json feat: add user avatars with cropping and image lightbox 2026-04-11 10:08:05 -06:00
server.js first commit 2026-04-09 18:41:01 -06:00

README.md

Tunes

A self-contained music website: browse and listen to songs and playlists, with user accounts and an admin upload UI. Everything runs from a single Node.js process with an embedded SQLite database and local media storage — no external services required.

Features (v1)

  • Public browse & listen
    • Home page with featured playlists and recently added songs
    • Paginated, searchable song list (title / artist / album)
    • Playlist grid and detail view with "Play all"
    • Persistent bottom-of-page audio player with queue support
    • HTTP Range streaming, so seeking/scrubbing works correctly
  • User accounts
    • Email + password registration (argon2id hashing)
    • Login / logout with SQLite-backed sessions (survive restarts)
    • Account page: update display name, change password
    • CSRF protection on all state-changing routes
  • Admin
    • Upload songs with cover art; title/artist/album/genre/year metadata
    • Duration auto-extracted from the audio file
    • Create and edit playlists, add/remove tracks
    • Toggle songs and playlists between public and private
    • First user who registers with ADMIN_BOOTSTRAP_EMAIL is auto-promoted to admin (only while no admin exists yet)

Roadmap (not in v1)

  • Generate new music via an external API from within the app
  • Let logged-in users publish their own songs and create playlists
  • Tune into streaming radio stations

Tech stack

  • Node.js + Express 4 — server and routing
  • SQLite via better-sqlite3 — embedded database, WAL mode
  • EJS + express-ejs-layouts — server-rendered views
  • argon2 — password hashing
  • express-session + better-sqlite3-session-store — persistent sessions
  • multer — file uploads (audio + cover art)
  • music-metadata — extract duration from uploaded audio
  • csrf-sync, zod, pino — CSRF, validation, logging
  • htmx (vendored) — small sprinklings of interactivity, no build step

Getting started

Prerequisites

  • Node.js 18+ (tested on 18.19)
  • npm

Install and run

git clone <your-repo-url> tunes
cd tunes
npm install
cp .env.example .env
# Edit .env:
#   SESSION_SECRET=<a long random string>
#   ADMIN_BOOTSTRAP_EMAIL=<your email>
node server.js

The server boots at http://localhost:3000 and runs database migrations automatically against tunes.db in the project root.

For auto-reload during development:

npm run dev

Creating the first admin

  1. Open http://localhost:3000/register
  2. Register with the email you set as ADMIN_BOOTSTRAP_EMAIL in .env. That first matching registration is automatically given the admin role.
  3. Once logged in, visit /admin/songs to upload audio and /admin/playlists to create playlists.

Any subsequent registrations are normal users.

Adding music

  1. Go to /admin/songs/new
  2. Fill in title, artist, and (optionally) album/genre/year
  3. Choose an audio file (mp3, m4a, ogg, flac, wav, opus, webm — up to 100 MB)
  4. Optionally add a cover image
  5. Check "Public" so visitors can see and play it, then click Upload

Uploaded files live under media/audio/ and media/covers/. The database stores relative paths, so you can move the whole project folder freely as long as media/ and tunes.db stay together.

Configuration

All settings come from environment variables (loaded from .env via dotenv):

Variable Default Purpose
PORT 3000 HTTP port
SESSION_SECRET (insecure default) Session cookie signing secret — set this in production
ADMIN_BOOTSTRAP_EMAIL (unset) Email that auto-becomes admin on first registration
DB_PATH ./tunes.db SQLite database file
MEDIA_DIR ./media Root directory for audio and cover uploads

Project layout

tunes/
├── server.js               # entry point: loads env, runs migrations, starts app
├── db/
│   ├── index.js            # SQLite connection + migration runner
│   └── migrations/*.sql    # schema migrations
├── src/
│   ├── app.js              # Express wiring (sessions, CSRF, views, routes)
│   ├── middleware/auth.js  # requireUser / requireAdmin
│   ├── routes/
│   │   ├── public.js       # /, /songs, /playlists, /stream/:id
│   │   ├── auth.js         # /register, /login, /logout
│   │   ├── account.js      # /account
│   │   └── admin.js        # /admin/songs, /admin/playlists
│   ├── services/           # users, songs, playlists (DB access)
│   └── views/              # EJS templates
├── public/                 # static CSS, JS, vendored htmx
└── media/                  # runtime audio + cover uploads (git-ignored)

Data and backups

Everything you care about lives in two places:

  • tunes.db — users, songs, playlists, sessions
  • media/ — audio files and cover art

Back up both together to preserve the full state of the site.