| name | backend-guide |
| description | 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. |
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/.