Skip to main content

Packages & Apps

Litara is a Turborepo monorepo with two apps and one shared package.

Apps

apps/api@litara/api

The NestJS backend. Serves the REST API, handles library scanning, and serves the built web frontend as static files in production.

Key responsibilities:

  • REST API with URI versioning (/api/v1/...)
  • JWT authentication (Passport local + JWT strategies)
  • Library scanning via fast-glob (initial scan) and chokidar (file watching)
  • Metadata extraction from ebook files
  • Optional metadata enrichment via Google Books API
  • OPDS v1 and v2 catalog endpoints
  • Swagger UI at /docs
  • Automatic Prisma migrations on startup

Notable dependencies:

PackagePurpose
@nestjs/*Framework, JWT, Swagger, serve-static
prisma + @prisma/adapter-pgDatabase ORM (Prisma 7 with pg driver adapter)
epub2EPUB metadata extraction
@litara/mobi-parserMOBI/AZW metadata extraction (internal package)
@litara/cbz-parserCBZ metadata and cover extraction (internal package, beta)
chokidarFile system watcher
fast-globHigh-performance file globbing for initial library scan
helmetHTTP security headers
bcryptPassword hashing

Testing: Jest for unit tests, Jest + Testcontainers for e2e tests (spins up a real PostgreSQL container — no mocking).


apps/web@litara/web

The React frontend. A single-page app served by Vite in development and by the NestJS static file server in production.

Key responsibilities:

  • JWT-based auth (token stored in localStorage)
  • All API calls through a shared Axios instance at src/utils/api.ts (/api/v1 base URL, auto-attaches Bearer token, redirects to /login on 401)
  • Library browsing, book detail pages, reading progress, shelves

Notable dependencies:

PackagePurpose
react 19 + react-domUI framework
react-router-dom v7Client-side routing
@mantine/core + @mantine/hooksUI component library (v8)
@tabler/icons-reactIcon set
axiosHTTP client
jotaiLightweight atomic state management
dompurifySanitises ebook description HTML before rendering

Dev tooling: Vite + @vitejs/plugin-react, TypeScript, ESLint, PostCSS with postcss-preset-mantine.


Packages

packages/mobi-parser@litara/mobi-parser

A custom TypeScript MOBI/AZW parser with no external runtime dependencies. It reads the binary PalmDB/MOBI/EXTH format directly and extracts metadata and cover art from .mobi, .azw, and .azw3 files, including AZW3 (KF8) boundary handling.

Exports two functions:

import { extractMobiMetadata, extractMobiCover } from '@litara/mobi-parser';

const metadata = await extractMobiMetadata('/path/to/book.mobi');
// → { title, authors, description, publishedDate, publisher, isbn, asin }

const cover = await extractMobiCover('/path/to/book.mobi');
// → Buffer | undefined

Why a separate package? NestJS compiles to CommonJS but the package uses Node ESM ("type": "module"). Isolating it as a workspace package lets it build independently and be consumed by apps/api as a workspace dependency ("@litara/mobi-parser": "*").


packages/cbz-parser@litara/cbz-parser (beta)

A TypeScript CBZ (comic book archive) parser. CBZ files are ZIP archives containing images and an optional ComicInfo.xml metadata file (the ComicRack standard). This package reads the ZIP with adm-zip and parses ComicInfo.xml with fast-xml-parser.

Exports two functions:

import { extractCbzMetadata, extractCbzCover } from '@litara/cbz-parser';

const metadata = extractCbzMetadata('/path/to/book.cbz');
// → { title, authors, description, publishedDate, publisher, language, subjects, series, seriesNumber, ids }

const cover = extractCbzCover('/path/to/book.cbz');
// → Buffer | undefined

Metadata mapping from ComicInfo.xml:

ComicInfo fieldMapped to
Titletitle
Writerauthors (split on ,)
Publisherpublisher
Summarydescription
LanguageISOlanguage
Year/Month/DaypublishedDate
Genresubjects (split on ,)
Tagssubjects (appended, split on ,)
Seriesids.series
Numberids.seriesNumber

If no ComicInfo.xml is present the title falls back to the filename. Cover extraction returns the image tagged as FrontCover in <Pages>, or the first alphabetically-sorted image in the archive.

Beta: CBZ support is functional but not yet validated against a broad range of real-world CBZ files. Edge cases may exist for non-standard archives.

Why a separate package? Same reason as mobi-parser — ESM isolation from the NestJS CommonJS build. The Jest e2e suite uses a CJS stub (test/mocks/cbz-parser.js) that replicates the same logic using adm-zip and fast-xml-parser directly.


Monorepo Tooling

ToolRole
TurborepoTask orchestration and build caching across workspaces
npm workspacesDependency hoisting and cross-package linking
semantic-releaseAutomated versioning and changelog from conventional commits
commitlintEnforces conventional commit format
PrettierCode formatting
prekGit hooks runner (pre-commit, pre-push, commit-msg)