Backend Architecture Overview
The backend lives in lightly_studio/src/lightly_studio. It is a Python package that can run as a local app, expose a FastAPI server, and serve the built web UI.
Core technologies
FastAPI provides the HTTP API and application lifecycle.
Pydantic is used for request and response models, validation, and OpenAPI schema generation.
SQLModel is used for database models and typed database access on top of SQLAlchemy sessions.
- The database layer supports
DuckDB by default and PostgreSQL as an alternative backend.
uvicorn runs the backend server.
Main packages
api/: FastAPI app setup, route registration, exception handling, media endpoints, and webapp serving. api/app.py is the composition root.
services/: Small orchestration layer for workflows that span multiple resolvers or need branching business logic. Not every endpoint needs a service.
resolvers/: Database-facing query and mutation functions. This is where most persistence logic lives.
models/: SQLModel tables plus Pydantic/SQLModel request and view models shared across layers.
core/, dataset/, export/, metadata/, plugins/, few_shot_classifier/: Product-specific modules used by routes, services, or resolvers when the logic is not just CRUD.
Model organization
Backend models are usually split by role within the same module:
*Base: shared fields
*Create: input model for inserts
*Table: SQLModel table mapped to the database
*View: API-facing response model
*WithCount or similar wrappers: list responses with pagination or metadata
Keep database tables and API views separate even when they look similar. This keeps persistence concerns, validation, and response shaping explicit.
Request flow
Most request paths follow this shape:
FastAPI route -> optional service -> resolver(s) -> SQLModel / database
Use the layers with the following intent:
- Routes translate HTTP input into typed models, wire dependencies, and map failures to HTTP responses.
- Services coordinate multiple resolvers or enforce workflow-specific rules.
- Resolvers own database access and reusable queries.
Thin endpoints may call resolvers directly. Services are mainly for cross-entity operations, not as a mandatory wrapper around every route.
Error handling
- Raise specific exceptions in the
api/ layer. FastAPI will handle converting them to HTTP responses. Do not raise HTTPException directly.
- We let exceptions raised from the rest of Python code propagate cleanly to the api layer, and be ultimately handled by FastAPI.
Runtime, persistence and the database
db_manager.py centralizes engine and session management.
- FastAPI dependencies provide short-lived sessions for request handling.
- The app lifespan also initializes and shuts down plugins, then closes the database engine cleanly.
- Python API classes in
core/ use a long-lived db_manager.persistent_session(). Currently this is a design limitation, causing issues with DuckDB's single-writer model.
DuckDB
Schema is created with SQLModel.metadata.create_all() on startup.
PostgreSQL and Alembic
See lightly_studio/MIGRATIONS.md for full details on Alembic setup, startup behavior, adding schema changes, and validation.
Build and generated artifacts
- The Python package is built from
lightly_studio/pyproject.toml with uv build.
- The backend package build depends on
make build-lightly_studio_view, which first exports backend-generated artifacts and then builds the frontend.
- OpenAPI is generated from the FastAPI app with
uv run src/lightly_studio/export_schema.py, which serializes app.openapi() to openapi.json.
- The frontend uses that generated schema for API type generation before its own build.
- After
lightly_studio_view is built, its static output is copied into lightly_studio/src/lightly_studio/dist_lightly_studio_view_app.
api/routes/webapp.py serves that bundled frontend from inside the Python package, so the shipped backend can serve the UI directly.
How to navigate the codebase
- Start in
api/routes/ if the change is triggered by an HTTP endpoint.
- Check
services/ when the endpoint coordinates multiple entities or sample types.
- Go to
resolvers/ for query logic, filtering, and persistence details.
- Look in
models/ for request bodies, response models, and database tables.
- Tests mirror this split under
lightly_studio/tests/.
1---2name: backend-guide3description: Read before adding or changing backend code in lightly_studio - FastAPI routes, services, resolvers, SQLModel tables, or database access. Explains the api/services/resolvers/models layering, the Base/Create/Table/View model split, request flow, error handling, DuckDB and PostgreSQL persistence, Alembic migrations, and how to navigate the package.4---56# Backend Architecture Overview78The backend lives in `lightly_studio/src/lightly_studio`. It is a Python package that can run as a local app, expose a FastAPI server, and serve the built web UI.910## Core technologies1112- `FastAPI` provides the HTTP API and application lifecycle.13- `Pydantic` is used for request and response models, validation, and OpenAPI schema generation.14- `SQLModel` is used for database models and typed database access on top of SQLAlchemy sessions.15- The database layer supports `DuckDB` by default and `PostgreSQL` as an alternative backend.16- `uvicorn` runs the backend server.1718## Main packages1920- `api/`: FastAPI app setup, route registration, exception handling, media endpoints, and webapp serving. `api/app.py` is the composition root.21- `services/`: Small orchestration layer for workflows that span multiple resolvers or need branching business logic. Not every endpoint needs a service.22- `resolvers/`: Database-facing query and mutation functions. This is where most persistence logic lives.23- `models/`: SQLModel tables plus Pydantic/SQLModel request and view models shared across layers.24- `core/`, `dataset/`, `export/`, `metadata/`, `plugins/`, `few_shot_classifier/`: Product-specific modules used by routes, services, or resolvers when the logic is not just CRUD.2526## Model organization2728Backend models are usually split by role within the same module:2930- `*Base`: shared fields31- `*Create`: input model for inserts32- `*Table`: SQLModel table mapped to the database33- `*View`: API-facing response model34- `*WithCount` or similar wrappers: list responses with pagination or metadata3536Keep database tables and API views separate even when they look similar. This keeps persistence concerns, validation, and response shaping explicit.3738## Request flow3940Most request paths follow this shape:4142```text43FastAPI route -> optional service -> resolver(s) -> SQLModel / database44```4546Use the layers with the following intent:4748- Routes translate HTTP input into typed models, wire dependencies, and map failures to HTTP responses.49- Services coordinate multiple resolvers or enforce workflow-specific rules.50- Resolvers own database access and reusable queries.5152Thin endpoints may call resolvers directly. Services are mainly for cross-entity operations, not as a mandatory wrapper around every route.5354## Error handling5556- Raise specific exceptions in the `api/` layer. FastAPI will handle converting them to HTTP responses. Do not raise `HTTPException` directly.57- We let exceptions raised from the rest of Python code propagate cleanly to the api layer, and be ultimately handled by FastAPI.5859## Runtime, persistence and the database6061- `db_manager.py` centralizes engine and session management.62- FastAPI dependencies provide short-lived sessions for request handling.63- The app lifespan also initializes and shuts down plugins, then closes the database engine cleanly.64- Python API classes in `core/` use a long-lived `db_manager.persistent_session()`. Currently this is a design limitation, causing issues with DuckDB's single-writer model.6566### DuckDB6768Schema is created with `SQLModel.metadata.create_all()` on startup.6970### PostgreSQL and Alembic7172See `lightly_studio/MIGRATIONS.md` for full details on Alembic setup, startup behavior, adding schema changes, and validation.7374## Build and generated artifacts7576- The Python package is built from `lightly_studio/pyproject.toml` with `uv build`.77- The backend package build depends on `make build-lightly_studio_view`, which first exports backend-generated artifacts and then builds the frontend.78- OpenAPI is generated from the FastAPI app with `uv run src/lightly_studio/export_schema.py`, which serializes `app.openapi()` to `openapi.json`.79- The frontend uses that generated schema for API type generation before its own build.80- After `lightly_studio_view` is built, its static output is copied into `lightly_studio/src/lightly_studio/dist_lightly_studio_view_app`.81- `api/routes/webapp.py` serves that bundled frontend from inside the Python package, so the shipped backend can serve the UI directly.8283## How to navigate the codebase8485- Start in `api/routes/` if the change is triggered by an HTTP endpoint.86- Check `services/` when the endpoint coordinates multiple entities or sample types.87- Go to `resolvers/` for query logic, filtering, and persistence details.88- Look in `models/` for request bodies, response models, and database tables.89- Tests mirror this split under `lightly_studio/tests/`.