# libexhq/libex architecture

> Architecture diagram of the GitHub repository libexhq/libex, generated by GitDiagram from its file tree, README and a sample of source files (last updated 2026-06-16). It is an AI-made overview: check details against the source.

- Interactive diagram: https://gitdiagram.com/libexhq/libex
- Repository: https://github.com/libexhq/libex

## Overview

1. **What this service is**
   Libex is a Python API service that fronts Audible metadata. The core architecture is a FastAPI application that accepts book/author/series/narrator/search requests, fetches fresh data from Audible when needed, persists successful responses in PostgreSQL, and exposes a separate set of endpoints that query the local library only.

2. **Primary request path**
   Most public endpoints follow the same flow: HTTP request → route layer with Pydantic schemas → service layer → cache/database lookup → Audible fetch if needed → normalize/enrich response → write to PostgreSQL → return response. That “fetch fresh, then persist” pattern is central to the design.

3. **API boundary and routing**
   `app/api/routes` is organized by resource: `books`, `authors`, `series`, `narrators`, `search`, `db`, `internal`, and `health/db` utility routes. The route layer is the external contract and also handles compatibility details like region-prefixed search paths for Audiobookshelf and legacy endpoint aliases.

4. **Audible integration boundary**
   `app/services/audible` contains the outbound integration with Audible. It is split by concern (`client`, `books`, `authors`, `series`, `search`) so the API layer does not talk to Audible directly. This is the main external dependency for live metadata, and it is the source of truth when data is requested.

5. **Persistent data layer**
   `app/db` defines the relational model, async session management, and base metadata. PostgreSQL is the only backing datastore. The schema stores books, authors, series, narrators, genres, pivots/relationships, cache entries, and enrichment fields such as narrator profile metadata, plans, and virtual voice flags.

6. **Read/write separation**
   `app/services/db/reader.py` and `writer.py` separate query logic from mutation logic. Readers power the `/db/*` endpoints and fallback lookup paths; writers persist freshly fetched Audible objects and related relationships. This boundary makes it easier to reason about “local library query” versus “ingest/update” behavior.

7. **Cache subsystem**
   `app/services/cache/manager.py` implements an application-level cache backed by PostgreSQL, not Redis. Cache behavior is configurable via TTL and enable/disable flags. It sits between request handling and external fetches to reduce repeated Audible calls, but the local database remains the durable record even after cache expiry.

8. **Background ingestion / seeding**
   `app/services/seeder.py` supports a background expansion process that walks relationships and discovers additional content over time. It is controlled by environment flags and intervals, and it uses the same Audible and database services as the request path. The `internal` API route likely provides a protected way to trigger or manage this behavior.

9. **Cross-cutting concerns**
   `app/core` contains configuration, middleware, logging, exception handling, and shared utilities. These modules define the operational envelope around the API: environment-driven config, request/response middleware, structured logging, and consistent API error mapping.

10. **Schema evolution and deployments**
   The repository is migration-driven with Alembic under `migrations/`, indicating a stable relational schema that evolves over time. The `Dockerfile`, `docker-compose.yml`, and GitHub Actions workflows show that the service is meant to run as a containerized API with PostgreSQL, with CI covering tests and publication.

11. **Data enrichment**
   Narrator records are not just mirrored from Audible; they are augmented with external profile data from NarratorList/AussieNarrator when available. That enrichment is persisted and then exposed through the DB/narrator endpoints, adding a secondary metadata source to the main Audible pipeline.

12. **Testing boundaries**
   The `tests/` tree mirrors the main subsystems: API routes, services, cache behavior, DB reader/writer, exceptions, and health checks. That structure reflects the architecture: contract tests at the HTTP layer, unit tests around service orchestration, and lower-level tests for persistence and utilities.

Main entry points: [`app/main.py`](https://github.com/libexhq/libex/blob/main/app/main.py) (App entry), [`app/services/db/writer.py`](https://github.com/libexhq/libex/blob/main/app/services/db/writer.py) (DB writer), [`app/api/routes/books/router.py`](https://github.com/libexhq/libex/blob/main/app/api/routes/books/router.py) (Books API), [`app/services/audible/client.py`](https://github.com/libexhq/libex/blob/main/app/services/audible/client.py) (Audible client)

## Diagram (Mermaid)

```mermaid
flowchart TD

subgraph group_api["API layer"]
  node_app_main["App entry<br/>FastAPI app<br/>[main.py]"]
  node_api_books_router["Books API<br/>route<br/>[router.py]"]
  node_api_books_schemas["Book schema<br/>pydantic<br/>[schemas.py]"]
  node_api_authors_router["Authors API<br/>route<br/>[router.py]"]
  node_api_authors_schemas["Author schema<br/>pydantic<br/>[schemas.py]"]
  node_api_series_router["Series API<br/>route<br/>[router.py]"]
  node_api_series_schemas["Series schema<br/>pydantic<br/>[schemas.py]"]
  node_api_narrators_router["Narrators API<br/>route<br/>[router.py]"]
  node_api_narrators_schemas["Narrator schema<br/>pydantic<br/>[schemas.py]"]
  node_api_search_router["Search API<br/>route<br/>[router.py]"]
  node_api_search_schemas["Search schema<br/>pydantic<br/>[schemas.py]"]
  node_api_db_router["DB API<br/>route<br/>[router.py]"]
  node_api_internal_router["Internal API<br/>route<br/>[router.py]"]
end

subgraph group_services["Services"]
  node_services_audible_client["Audible client<br/>integration client<br/>[client.py]"]
  node_services_audible_books["Books fetch<br/>audible sync<br/>[books.py]"]
  node_services_audible_authors["Authors fetch<br/>audible sync<br/>[authors.py]"]
  node_services_audible_series["Series fetch<br/>audible sync<br/>[series.py]"]
  node_services_audible_search["Search fetch<br/>audible sync<br/>[search.py]"]
  node_services_cache_manager["Cache manager<br/>postgres cache<br/>[manager.py]"]
  node_services_db_reader["DB reader<br/>query service<br/>[reader.py]"]
  node_services_db_writer["DB writer<br/>write service<br/>[writer.py]"]
  node_services_seeder["Seeder<br/>background ingest<br/>[seeder.py]"]
end

subgraph group_data["Data layer"]
  node_db_models[("Models<br/>orm schema<br/>[models.py]")]
  node_db_session["Session<br/>async db<br/>[session.py]"]
  node_db_base["Base<br/>[base.py]"]
  node_migrations["Migrations<br/>alembic<br/>[env.py]"]
end

subgraph group_core["Core"]
  node_core_config["Config<br/>settings<br/>[config.py]"]
  node_core_middleware["Middleware<br/>[middleware.py]"]
  node_core_exceptions["Exceptions<br/>error mapping<br/>[exceptions.py]"]
end

subgraph group_ops["Ops"]
  node_ops_dockerfile["Dockerfile<br/>container build"]
  node_ops_compose["Compose<br/>dev stack<br/>[docker-compose.yml]"]
  node_ops_ci["CI workflows<br/>github actions<br/>[tests.yml]"]
end

node_app_main -->|"mounts"| node_api_books_router
node_app_main -->|"mounts"| node_api_authors_router
node_app_main -->|"mounts"| node_api_series_router
node_app_main -->|"mounts"| node_api_narrators_router
node_app_main -->|"mounts"| node_api_search_router
node_app_main -->|"mounts"| node_api_db_router
node_app_main -->|"mounts"| node_api_internal_router
node_app_main -->|"uses"| node_core_config
node_app_main -->|"wraps"| node_core_middleware
node_app_main -->|"maps"| node_core_exceptions
node_api_books_router -->|"validates"| node_api_books_schemas
node_api_books_router -->|"checks"| node_services_cache_manager
node_api_books_router -->|"fetches"| node_services_audible_books
node_api_books_router -->|"persists"| node_services_db_writer
node_api_authors_router -->|"validates"| node_api_authors_schemas
node_api_authors_router -->|"fetches"| node_services_audible_authors
node_api_authors_router -->|"persists"| node_services_db_writer
node_api_series_router -->|"validates"| node_api_series_schemas
node_api_series_router -->|"fetches"| node_services_audible_series
node_api_series_router -->|"persists"| node_services_db_writer
node_api_narrators_router -->|"validates"| node_api_narrators_schemas
node_api_narrators_router -->|"reads"| node_services_db_reader
node_api_search_router -->|"validates"| node_api_search_schemas
node_api_search_router -->|"checks"| node_services_cache_manager
node_api_search_router -->|"fetches"| node_services_audible_search
node_api_db_router -->|"reads"| node_services_db_reader
node_api_internal_router -->|"controls"| node_services_seeder
node_services_cache_manager -->|"uses"| node_db_session
node_services_cache_manager -->|"reads/writes"| node_db_models
node_services_db_reader -->|"queries"| node_db_session
node_services_db_writer -->|"mutates"| node_db_session
node_services_db_writer -->|"maps"| node_db_models
node_services_audible_client -->|"configures"| node_core_config
node_services_audible_books -->|"calls"| node_services_audible_client
node_services_audible_authors -->|"calls"| node_services_audible_client
node_services_audible_series -->|"calls"| node_services_audible_client
node_services_audible_search -->|"calls"| node_services_audible_client
node_services_seeder -->|"discovers"| node_services_audible_books
node_services_seeder -->|"reads"| node_services_db_reader
node_services_seeder -->|"persists"| node_services_db_writer
node_db_models -->|"extends"| node_db_base
node_db_session -->|"binds"| node_db_base
node_migrations -.->|"evolves"| node_db_models
node_ops_compose -.->|"provisions"| node_db_session
node_ops_dockerfile -.->|"packages"| node_app_main
node_ops_ci -.->|"verifies"| node_ops_dockerfile

click node_app_main "https://github.com/libexhq/libex/blob/main/app/main.py"
click node_api_books_router "https://github.com/libexhq/libex/blob/main/app/api/routes/books/router.py"
click node_api_books_schemas "https://github.com/libexhq/libex/blob/main/app/api/routes/books/schemas.py"
click node_api_authors_router "https://github.com/libexhq/libex/blob/main/app/api/routes/authors/router.py"
click node_api_authors_schemas "https://github.com/libexhq/libex/blob/main/app/api/routes/authors/schemas.py"
click node_api_series_router "https://github.com/libexhq/libex/blob/main/app/api/routes/series/router.py"
click node_api_series_schemas "https://github.com/libexhq/libex/blob/main/app/api/routes/series/schemas.py"
click node_api_narrators_router "https://github.com/libexhq/libex/blob/main/app/api/routes/narrators/router.py"
click node_api_narrators_schemas "https://github.com/libexhq/libex/blob/main/app/api/routes/narrators/schemas.py"
click node_api_search_router "https://github.com/libexhq/libex/blob/main/app/api/routes/search/router.py"
click node_api_search_schemas "https://github.com/libexhq/libex/blob/main/app/api/routes/search/schemas.py"
click node_api_db_router "https://github.com/libexhq/libex/blob/main/app/api/routes/db/router.py"
click node_api_internal_router "https://github.com/libexhq/libex/blob/main/app/api/routes/internal/router.py"
click node_core_config "https://github.com/libexhq/libex/blob/main/app/core/config.py"
click node_core_middleware "https://github.com/libexhq/libex/blob/main/app/core/middleware.py"
click node_core_exceptions "https://github.com/libexhq/libex/blob/main/app/core/exceptions.py"
click node_services_audible_client "https://github.com/libexhq/libex/blob/main/app/services/audible/client.py"
click node_services_audible_books "https://github.com/libexhq/libex/blob/main/app/services/audible/books.py"
click node_services_audible_authors "https://github.com/libexhq/libex/blob/main/app/services/audible/authors.py"
click node_services_audible_series "https://github.com/libexhq/libex/blob/main/app/services/audible/series.py"
click node_services_audible_search "https://github.com/libexhq/libex/blob/main/app/services/audible/search.py"
click node_services_cache_manager "https://github.com/libexhq/libex/blob/main/app/services/cache/manager.py"
click node_services_db_reader "https://github.com/libexhq/libex/blob/main/app/services/db/reader.py"
click node_services_db_writer "https://github.com/libexhq/libex/blob/main/app/services/db/writer.py"
click node_services_seeder "https://github.com/libexhq/libex/blob/main/app/services/seeder.py"
click node_db_models "https://github.com/libexhq/libex/blob/main/app/db/models.py"
click node_db_session "https://github.com/libexhq/libex/blob/main/app/db/session.py"
click node_db_base "https://github.com/libexhq/libex/blob/main/app/db/base.py"
click node_migrations "https://github.com/libexhq/libex/blob/main/migrations/env.py"
click node_ops_dockerfile "https://github.com/libexhq/libex/tree/main/Dockerfile"
click node_ops_compose "https://github.com/libexhq/libex/blob/main/docker-compose.yml"
click node_ops_ci "https://github.com/libexhq/libex/blob/main/.github/workflows/tests.yml"

classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_app_main,node_api_books_router,node_api_books_schemas,node_api_authors_router,node_api_authors_schemas,node_api_series_router,node_api_series_schemas,node_api_narrators_router,node_api_narrators_schemas,node_api_search_router,node_api_search_schemas,node_api_db_router,node_api_internal_router toneBlue
class node_services_audible_client,node_services_audible_books,node_services_audible_authors,node_services_audible_series,node_services_audible_search,node_services_cache_manager,node_services_db_reader,node_services_db_writer,node_services_seeder toneAmber
class node_db_models,node_db_session,node_db_base,node_migrations toneMint
class node_core_config,node_core_middleware,node_core_exceptions toneRose
class node_ops_dockerfile,node_ops_compose,node_ops_ci toneIndigo
```

## Components

### API layer

FastAPI routes and request schemas

- [App entry](https://github.com/libexhq/libex/blob/main/app/main.py): FastAPI app · Application bootstrap and route wiring · `app/main.py`
- [Books API](https://github.com/libexhq/libex/blob/main/app/api/routes/books/router.py): route · Book request endpoints and compatibility flow · `app/api/routes/books/router.py`
- [Book schema](https://github.com/libexhq/libex/blob/main/app/api/routes/books/schemas.py): pydantic · Request and response models for books · `app/api/routes/books/schemas.py`
- [Authors API](https://github.com/libexhq/libex/blob/main/app/api/routes/authors/router.py): route · Author request endpoints · `app/api/routes/authors/router.py`
- [Author schema](https://github.com/libexhq/libex/blob/main/app/api/routes/authors/schemas.py): pydantic · Request and response models for authors · `app/api/routes/authors/schemas.py`
- [Series API](https://github.com/libexhq/libex/blob/main/app/api/routes/series/router.py): route · Series request endpoints · `app/api/routes/series/router.py`
- [Series schema](https://github.com/libexhq/libex/blob/main/app/api/routes/series/schemas.py): pydantic · Request and response models for series · `app/api/routes/series/schemas.py`
- [Narrators API](https://github.com/libexhq/libex/blob/main/app/api/routes/narrators/router.py): route · Narrator request endpoints · `app/api/routes/narrators/router.py`
- [Narrator schema](https://github.com/libexhq/libex/blob/main/app/api/routes/narrators/schemas.py): pydantic · Request and response models for narrators · `app/api/routes/narrators/schemas.py`
- [Search API](https://github.com/libexhq/libex/blob/main/app/api/routes/search/router.py): route · Search endpoints including region-prefixed paths · `app/api/routes/search/router.py`
- [Search schema](https://github.com/libexhq/libex/blob/main/app/api/routes/search/schemas.py): pydantic · Request and response models for search · `app/api/routes/search/schemas.py`
- [DB API](https://github.com/libexhq/libex/blob/main/app/api/routes/db/router.py): route · Local-library query endpoints · `app/api/routes/db/router.py`
- [Internal API](https://github.com/libexhq/libex/blob/main/app/api/routes/internal/router.py): route · Protected operational and ingestion endpoints · `app/api/routes/internal/router.py`

### Services

External integration, cache, persistence orchestration, and ingestion

- [Audible client](https://github.com/libexhq/libex/blob/main/app/services/audible/client.py): integration client · Outbound Audible API access · `app/services/audible/client.py`
- [Books fetch](https://github.com/libexhq/libex/blob/main/app/services/audible/books.py): audible sync · Book-specific Audible retrieval and normalization · `app/services/audible/books.py`
- [Authors fetch](https://github.com/libexhq/libex/blob/main/app/services/audible/authors.py): audible sync · Author-specific Audible retrieval and normalization · `app/services/audible/authors.py`
- [Series fetch](https://github.com/libexhq/libex/blob/main/app/services/audible/series.py): audible sync · Series-specific Audible retrieval and normalization · `app/services/audible/series.py`
- [Search fetch](https://github.com/libexhq/libex/blob/main/app/services/audible/search.py): audible sync · Audible search and lookup support · `app/services/audible/search.py`
- [Cache manager](https://github.com/libexhq/libex/blob/main/app/services/cache/manager.py): postgres cache · Application cache backed by PostgreSQL · `app/services/cache/manager.py`
- [DB reader](https://github.com/libexhq/libex/blob/main/app/services/db/reader.py): query service · Read paths for local-library and fallback lookups · `app/services/db/reader.py`
- [DB writer](https://github.com/libexhq/libex/blob/main/app/services/db/writer.py): write service · Persistence for freshly fetched Audible objects and relationships · `app/services/db/writer.py`
- [Seeder](https://github.com/libexhq/libex/blob/main/app/services/seeder.py): background ingest · Relationship-walking expansion and background seeding · `app/services/seeder.py`

### Data layer

PostgreSQL models, session management, and migrations

- [Models](https://github.com/libexhq/libex/blob/main/app/db/models.py): orm schema · Relational entities for books, authors, series, narrators, genres, pivots, and cache · `app/db/models.py`
- [Session](https://github.com/libexhq/libex/blob/main/app/db/session.py): async db · Async session and connection management · `app/db/session.py`
- [Base](https://github.com/libexhq/libex/blob/main/app/db/base.py): orm base · Shared SQLAlchemy metadata/base definitions · `app/db/base.py`
- [Migrations](https://github.com/libexhq/libex/blob/main/migrations/env.py): alembic · Schema evolution history · `migrations/env.py`

### Core

Configuration, middleware, logging, and shared utilities

- [Config](https://github.com/libexhq/libex/blob/main/app/core/config.py): settings · Environment-driven application settings · `app/core/config.py`
- [Middleware](https://github.com/libexhq/libex/blob/main/app/core/middleware.py): http middleware · Request/response interception and cross-cutting HTTP behavior · `app/core/middleware.py`
- [Exceptions](https://github.com/libexhq/libex/blob/main/app/core/exceptions.py): error mapping · Consistent API error handling · `app/core/exceptions.py`

### Ops

Containerization and deployment support

- [Dockerfile](https://github.com/libexhq/libex/tree/main/Dockerfile): container build · Container image definition · `Dockerfile`
- [Compose](https://github.com/libexhq/libex/blob/main/docker-compose.yml): dev stack · Local service composition with PostgreSQL · `docker-compose.yml`
- [CI workflows](https://github.com/libexhq/libex/blob/main/.github/workflows/tests.yml): github actions · Tests, publish, and cleanup automation · `.github/workflows/tests.yml`

## Connections

- App entry → Books API: mounts · Registers book endpoints
- App entry → Authors API: mounts · Registers author endpoints
- App entry → Series API: mounts · Registers series endpoints
- App entry → Narrators API: mounts · Registers narrator endpoints
- App entry → Search API: mounts · Registers search endpoints
- App entry → DB API: mounts · Registers local DB endpoints
- App entry → Internal API: mounts · Registers internal endpoints
- App entry → Config: uses · Loads runtime configuration
- App entry → Middleware: wraps · Adds HTTP middleware
- App entry → Exceptions: maps · Installs API error handling
- Books API → Book schema: validates · Uses request/response models
- Books API → Cache manager: checks · Looks up cached responses first
- Books API → Books fetch: fetches · Requests fresh Audible book data
- Books API → DB writer: persists · Stores freshly fetched book data
- Authors API → Author schema: validates · Uses request/response models
- Authors API → Authors fetch: fetches · Requests fresh Audible author data
- Authors API → DB writer: persists · Stores fetched author data
- Series API → Series schema: validates · Uses request/response models
- Series API → Series fetch: fetches · Requests fresh Audible series data
- Series API → DB writer: persists · Stores fetched series data
- Narrators API → Narrator schema: validates · Uses request/response models
- Narrators API → DB reader: reads · Serves local narrator data and enrichment
- Search API → Search schema: validates · Uses request/response models
- Search API → Cache manager: checks · Uses cache to reduce repeated searches
- Search API → Search fetch: fetches · Performs Audible search lookups
- DB API → DB reader: reads · Queries local library only
- Internal API → Seeder: controls · Triggers or manages background expansion
- Cache manager → Session: uses · Stores cache entries in PostgreSQL
- Cache manager → Models: reads/writes · Operates on cache records
- DB reader → Session: queries · Executes read-only database access
- DB writer → Session: mutates · Executes database writes
- DB writer → Models: maps · Persists ORM entities and pivots
- Audible client → Config: configures · Uses credentials and runtime settings
- Books fetch → Audible client: calls · Uses shared Audible transport
- Authors fetch → Audible client: calls · Uses shared Audible transport
- Series fetch → Audible client: calls · Uses shared Audible transport
- Search fetch → Audible client: calls · Uses shared Audible transport
- Seeder → Books fetch: discovers · Walks relationships by fetching more content
- Seeder → DB reader: reads · Uses local graph and existing records
- Seeder → DB writer: persists · Writes newly discovered data
- Models → Base: extends · Built on shared ORM base metadata
- Session → Base: binds · Connects metadata to engine/session
- Migrations → Models: evolves · Tracks schema changes for the ORM model set
- Compose → Session: provisions · Provides PostgreSQL backing service in dev
- Dockerfile → App entry: packages · Builds the API service container
- CI workflows → Dockerfile: verifies · CI covers build and test automation
