The end-to-end shape that scales to a real product
Putting the pieces together: a real FastAPI service with aiosqlite ends up with five clear layers.
- Lifespan — open store at startup, close at shutdown.
- Store — CRUD class wrapping aiosqlite. The only place SQL lives.
- Routes — FastAPI endpoints. Inject the store via Depends, call methods, return Pydantic models.
- Models — Pydantic schemas for request/response shapes.
- Background tasks — anything long-running (embedding generation, indexing) goes through
asyncio.create_taskor a job queue, not in the request/response path.
Self-reference: Pippa's backend exactly follows this shape:
main.py is the lifespan, store/conversations.py is the store, routes/chat.py is the routes, routes/models.py-style files hold Pydantic schemas, and the heartbeat scheduler runs as a long-lived background task.