Development
Prerequisites
Section titled “Prerequisites”- Docker and Docker Compose
- Node.js 22.18+
- pnpm 12 (
npm install -g pnpm)
Development Setup
Section titled “Development Setup”Librariarr uses Docker Compose for development to provide a PostgreSQL database and hot-reloading.
-
Clone the repository
Terminal window git clone https://github.com/ahembree/librariarr.gitcd librariarr -
Start the dev environment
Terminal window pnpm docker:devThis builds the dev container, starts PostgreSQL, pushes the Prisma schema to the dev database (
prisma db push— no migration files), and starts the Next.js dev server with Turbopack hot-reloading.No
.envfile is needed:docker-compose.dev.ymlsetsDATABASE_URLand a developmentSESSION_SECRETitself. Docker Compose does read a.envnext to it forTZ, so putTZ=…there to run scheduled jobs in your time zone. Copy.env.exampleto.envonly when running the app outside Docker (pnpm devagainst your own Postgres). -
Open the app
Navigate to
http://localhost:3000
Development Commands
Section titled “Development Commands”Docker
Section titled “Docker”| Command | Description |
|---|---|
pnpm docker:dev |
Start dev environment (foreground) |
pnpm docker:dev:detach |
Start dev environment (background) |
pnpm docker:dev:down |
Stop dev environment |
pnpm docker:dev:rebuild |
Rebuild containers from scratch |
pnpm docker:dev:clean |
Stop containers and delete DB volume |
pnpm docker:dev:logs |
Tail dev container logs |
Database
Section titled “Database”| Command | Description |
|---|---|
pnpm docker:dev:db:push |
Push schema changes (fast iteration, no migration files) |
pnpm docker:dev:db:migrate |
Run Prisma migrations |
pnpm docker:dev:db:studio |
Open Prisma Studio (DB browser) |
pnpm docker:dev:db:reset |
Reset DB completely |
Indexes outside the Prisma schema
Section titled “Indexes outside the Prisma schema”Most indexes are declared in prisma/schema.prisma, but a few exist only as SQL in a migration (prisma/migrations/0030_query_indexes), because Prisma cannot declare them: an expression index on MediaItem (type, LOWER(TRIM("parentTitle"))) for artist lookups, and three partial trigram (pg_trgm) indexes on title, parentTitle and albumTitle for title search. prisma db push neither creates nor drops them — it leaves alone an expression or partial index it cannot represent — so a production install, which runs the migrations and then db push, keeps them.
A database built by db push alone does not have them: the dev container (which pushes the schema on start), the test database and the end-to-end database. Queries return the same results without them, only slower, so a query plan or timing measured there is not what production sees. To add them to such a database (or to an install where pg_trgm became available after the migration had run), run:
CREATE INDEX IF NOT EXISTS "MediaItem_type_parentTitle_norm_idx" ON "MediaItem" ("type", LOWER(TRIM("parentTitle")));CREATE EXTENSION IF NOT EXISTS pg_trgm;CREATE INDEX IF NOT EXISTS "MediaItem_title_trgm_idx" ON "MediaItem" USING GIN ("title" gin_trgm_ops) WHERE "title" IS NOT NULL;CREATE INDEX IF NOT EXISTS "MediaItem_parentTitle_trgm_idx" ON "MediaItem" USING GIN ("parentTitle" gin_trgm_ops) WHERE "parentTitle" IS NOT NULL;CREATE INDEX IF NOT EXISTS "MediaItem_albumTitle_trgm_idx" ON "MediaItem" USING GIN ("albumTitle" gin_trgm_ops) WHERE "albumTitle" IS NOT NULL;Do not turn on Prisma’s partialIndexes preview feature: db push would then manage partial indexes itself and drop these, since the schema does not declare them.
Mock Data
Section titled “Mock Data”Populate the database with fictional media data for testing or taking screenshots without exposing personal data.
| Command | Description |
|---|---|
pnpm docker:dev:seed |
Seed the dev database with mock data |
Build & Lint
Section titled “Build & Lint”| Command | Description |
|---|---|
pnpm build |
Next.js production build |
pnpm lint |
ESLint |
pnpm exec prisma generate |
Regenerate Prisma client (needed after schema changes when DB isn’t available) |
Testing
Section titled “Testing”Tests use Vitest with a real PostgreSQL test database (librariarr_test), auto-created by the global setup.
Test Commands
Section titled “Test Commands”| Command | Description |
|---|---|
pnpm test |
Run full test suite |
pnpm test:watch |
Watch mode |
pnpm test:unit |
Unit tests only (no DB needed) |
pnpm test:integration |
Integration tests only (requires DB) |
pnpm test:coverage |
Run with coverage report |
pnpm exec vitest run tests/path/to/file.test.ts |
Run a single test file |
Test Structure
Section titled “Test Structure”tests/unit/— Pure logic tests (rules engine, filters, sync detection, formatting, caching, validation schemas)tests/unit/hooks/— React hook tests, run underhappy-dom(// @vitest-environment happy-domat the top of each file) withrenderHookfrom@testing-library/reacttests/integration/— API route handler tests against a real database with mocked external services
Browser end-to-end tests
Section titled “Browser end-to-end tests”Real-browser tests live in e2e/ and run with Playwright, separately from Vitest.
| Command | Description |
|---|---|
pnpm e2e:docker |
Build the production image and run the whole suite in Docker (Postgres, the app and the official Playwright image, browsers included) |
pnpm e2e:docker:down |
Remove the e2e containers and their database volume |
pnpm e2e:install |
Download Chromium for running on the host (needs access to cdn.playwright.dev) |
pnpm e2e |
Run the suite on the host, against the app pnpm build produced (run it first) |
pnpm e2e:ui |
Playwright’s interactive UI mode |
pnpm e2e:report |
Open the last HTML report |
The Playwright image’s tag is never written by hand: both Docker scripts set PLAYWRIGHT_VERSION from the @playwright/test version in pnpm-lock.yaml (scripts/playwright-version.mjs), because the image’s browsers only work with that exact library version. A bare docker compose -f docker-compose.e2e.yml up without it refuses to start.
Documentation site
Section titled “Documentation site”The documentation (this site) is an Astro + Starlight project in docs/, with its own package.json and lockfile.
| Command | Description |
|---|---|
pnpm docs:dev |
Start the docs dev server (http://localhost:4321) |
pnpm docs:build |
Production build |
pnpm docs:preview |
Preview the built site |
Both dev and build first validate the rule sets on the rule set library page and generate the API reference’s OpenAPI document from the app’s source.
Tech Stack
Section titled “Tech Stack”| Technology | Purpose |
|---|---|
| Next.js | App Router framework |
| React | UI library |
| TypeScript | Type safety |
| PostgreSQL | Database |
| Prisma | ORM |
| Tailwind CSS | Styling (OKLCH color model) |
| shadcn/ui | Component library (new-york style) |
| Vitest | Testing framework |
Exact versions live in package.json.
Git Conventions
Section titled “Git Conventions”- Pre-commit hook: Runs
pnpm exec eslint --quietvia Husky - Commit format: Conventional Commits enforced by commitlint
feat: add new featurefix(auth): handle expired tokensdocs: update installation guiderefactor: simplify rule evaluation
Project Structure
Section titled “Project Structure”src/├── app/ # Next.js App Router│ ├── (authenticated)/ # Protected pages (auth enforced by layout)│ ├── api/ # API routes│ ├── login/ # Login page│ └── onboarding/ # First-time setup├── components/│ └── ui/ # shadcn/ui primitives├── hooks/ # Custom React hooks└── lib/ ├── auth/ # Session management ├── arr/ # Sonarr/Radarr/Lidarr clients ├── backup/ # Backup & restore service ├── cache/ # In-memory cache ├── dedup/ # Multi-server deduplication ├── filters/ # Media filter utilities ├── jobs/ # Graphile Worker background job queue + scheduler ├── plex/ # Plex OAuth and API client ├── rules/ # Lifecycle rule engine ├── sync/ # Media sync engine └── validation.ts # Zod schemas for API validation