Skip to content

Development

  • Docker and Docker Compose
  • Node.js 22.18+
  • pnpm 12 (npm install -g pnpm)

Librariarr uses Docker Compose for development to provide a PostgreSQL database and hot-reloading.

  1. Clone the repository

    Terminal window
    git clone https://github.com/ahembree/librariarr.git
    cd librariarr
  2. Start the dev environment

    Terminal window
    pnpm docker:dev

    This 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 .env file is needed: docker-compose.dev.yml sets DATABASE_URL and a development SESSION_SECRET itself. Docker Compose does read a .env next to it for TZ, so put TZ=… there to run scheduled jobs in your time zone. Copy .env.example to .env only when running the app outside Docker (pnpm dev against your own Postgres).

  3. Open the app

    Navigate to http://localhost:3000

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
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

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.

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
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)

Tests use Vitest with a real PostgreSQL test database (librariarr_test), auto-created by the global setup.

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
  • tests/unit/ — Pure logic tests (rules engine, filters, sync detection, formatting, caching, validation schemas)
  • tests/unit/hooks/ — React hook tests, run under happy-dom (// @vitest-environment happy-dom at the top of each file) with renderHook from @testing-library/react
  • tests/integration/ — API route handler tests against a real database with mocked external services

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.

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.

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.

  • Pre-commit hook: Runs pnpm exec eslint --quiet via Husky
  • Commit format: Conventional Commits enforced by commitlint
    • feat: add new feature
    • fix(auth): handle expired tokens
    • docs: update installation guide
    • refactor: simplify rule evaluation
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