LitLocker is a self-hosted digital library project inspired by BookLore.
This repository contains the server/API for the project. It is designed as a web-first backend for managing EPUB, PDF, and comic libraries with a clean ports-and-adapters architecture, explicit contracts, and deployment paths that stay understandable for self-hosters.
The current server can:
- manage books with metadata, authors, tags, series, cover data, and file references
- manage manual shelves and shelf membership
- ingest uploads into an import pipeline
- extract and enrich import metadata through a metadata-provider seam
- detect duplicate imports
- store reading progress
- persist data in Postgres
- protect API routes with OIDC-backed auth when enabled
- expose structured logs, health checks, rate limits, upload validation, and shutdown gracefully
Current implemented route groups:
GET /health- books:
POST /booksGET /booksGET /books/:idPATCH /books/:id
- shelves:
POST /shelvesGET /shelvesPATCH /shelves/:idDELETE /shelves/:idPOST /shelves/:id/books/:bookIdDELETE /shelves/:id/books/:bookId
- imports:
POST /importsGET /importsGET /imports/:idPOST /imports/:id/finalize
- reading progress:
GET /progress/:bookIdPOST /progress
This is still an early server-first foundation, but it is not a toy skeleton. The project now has:
- real Postgres persistence
- migrations with
node-pg-migrate - self-hosting with docker
- health checks and structured logging
- import, browse, and reading-progress flows
Still intentionally not finished but planned:
- richer reader delivery flows
- OPDS and device sync
- broader ecosystem features
- more polished contributor and operator tooling
- Node.js
24 pnpm10- Postgres
15+or Docker
The repository includes:
Quick start:
cp .env.example .env
docker compose up --buildExpected result:
litlocker-serveris available onhttp://localhost:3000litlocker-postgresis available onlocalhost:15432- both containers have health checks
- the app runs pending migrations automatically on startup
For a Docker Compose deployment in Coolify, use docker-compose.coolify.yaml.
Notes:
- it is production-oriented, so it does not publish host ports directly
- Coolify should route traffic to the
litlocker-serverservice on container port3000 - required values such as
SERVER__HTTP__ADDRESS,DATABASE__PASSWORD, andAUTH__SESSION_SECRETare surfaced through Compose variable placeholders in Coolify's UI - if
AUTH__ENABLED=true, you must also provide the OIDC settings required by the app config validator
Persistent volumes in the compose stack:
- Postgres data
- library files
- import staging files
- cover files
If you want to run directly on the host:
cp .env.example .env
pnpm install
pnpm run startThe default local database values are:
- host:
localhost - port:
15432 - user:
devdb - password:
devpass - database:
devdb - schema:
litlocker
The app will:
- validate env configuration at startup
- run pending migrations
- start serving on
http://localhost:3000by default
All current environment variables are shown in .env.example.
SERVER__HTTP__ADDRESSSERVER__HTTP__PORTSERVER__HTTP__TIMEOUT_MS
STORAGE__PATHS__LIBRARYSTORAGE__PATHS__IMPORTSSTORAGE__PATHS__COVERS
These paths are used for:
- finalized library files
- temporary import staging
- stored cover assets
IMPORTS__MAX_FILE_SIZE_IN_BYTESIMPORTS__ALLOWED_FILE_EXTENSIONSIMPORTS__DUPLICATE_CHECK_ENABLEDIMPORTS__UPLOAD_RATE_LIMIT__WINDOW_MSIMPORTS__UPLOAD_RATE_LIMIT__MAX_REQUESTS
DATABASE__HOSTDATABASE__PORTDATABASE__USERDATABASE__PASSWORDDATABASE__DATABASEDATABASE__SCHEMADATABASE__SSL_ENABLEDDATABASE__POOL_MAX_CONNECTIONSDATABASE__POOL_IDLE_TIMEOUT_MSDATABASE__CONNECTION_TIMEOUT_MS
Important implementation details:
- the app uses the
litlockerPostgres schema by default - the project uses handwritten SQL via
pg - no ORM is used
- no database-native foreign keys are used
AUTH__ENABLEDAUTH__SESSION_SECRETAUTH__SESSION_TTL_MSAUTH__SESSION_COOKIE_NAMEAUTH__SESSION_COOKIE_SECUREAUTH__RATE_LIMIT__WINDOW_MSAUTH__RATE_LIMIT__MAX_REQUESTSAUTH__OIDC__ISSUER_URLAUTH__OIDC__CLIENT_IDAUTH__OIDC__CLIENT_SECRETAUTH__OIDC__REDIRECT_URLAUTH__OIDC__POST_LOGOUT_REDIRECT_URLAUTH__OIDC__SCOPESAUTH__OIDC__REQUIRE_PKCEAUTH__OIDC__DISCOVERY_TIMEOUT_MS
When auth is enabled:
/healthstays public- the rest of the API is protected
- OIDC is used so self-hosters can rely on an existing IdP
METADATA_PROVIDERS__ENABLED_PROVIDERSMETADATA_PROVIDERS__LOOKUP_TIMEOUT_MSMETADATA_PROVIDERS__DEFAULT_LANGUAGE
Migration scripts:
pnpm run migrate:uppnpm run migrate:downpnpm run migrate:create <name>
At runtime, the app also runs pending migrations automatically during boot.
pnpm run dev- start the server in watch mode
pnpm run start- start the server once
pnpm run start:debug- start with the Node inspector
pnpm run migrate:up- apply pending migrations
pnpm run migrate:down- roll back migrations
pnpm run migrate:create <name>- create a migration file
pnpm run test:unit- run unit tests
pnpm run test:integration- run integration tests
pnpm run lint:check- run oxlint
pnpm run lint:ts- run
tsc --noEmit
- run
pnpm run fmt:check- verify formatting
- Backup and restore guide: BACKUP_RESTORE.md
- Manual smoke checks: SMOKE_TESTS.md
- Contributor guide: CONTRIBUTING.md
The codebase follows a ports-and-adapters structure with explicit dependency injection.
Top-level layout:
src/application- application behavior, entity definitions, and interface contracts
src/adapters- infrastructure implementations such as HTTP, auth, config, logging, storage, and persistence
src/runtime- runtime-only boot and shutdown behavior
src/boot.js- dependency wiring
src/index.js- process entrypoint
Important conventions:
- JavaScript source with
.d.tscontracts - entity definitions live under
src/application/entities - interfaces live under
src/application/interfaces - adapters depend on application contracts, not the other way around
createXfactories and dependency injection- do not split ports into separate “driving” and “driven” taxonomies
For contributor-facing guidance, see CONTRIBUTING.md.
If you only want a fast local developer loop:
cp .env.example .env
pnpm install
pnpm run devTo sanity-check the app manually after changes:
- use SMOKE_TESTS.md
MIT