Skip to content

002. Backend application stack and tooling

  • Date: 2026-05-25
  • Status: Accepted

Extended by RAG003. Corpus ingestion - lean Parquet via huggingface-hub and pyarrow. Extended by RAG004. Lambda ASGI adapter - Mangum. Extended by RAG007. LLM model selection - Haiku-class via Bedrock.

  • In the context of building the rag-sample backend (corpus ingestion plus a query API) as a single Python codebase that runs locally and deploys to AWS Lambda, and that must reach Postgres both locally (direct connection) and on Aurora (via the RDS Data API, per RAG001),
  • facing the choice of web framework, database driver, schema-migration approach, configuration, packaging, and developer tooling for a small, cost-conscious, agent-developed service,
  • we decided for
  • and neglected
    • Django / Flask — heavier or less async-native than FastAPI for a thin JSON API on Lambda,
    • SQLAlchemy ORM with Alembic migrations — adds an ORM and migration layer that does not map cleanly onto the RDS Data API access path used in AWS; plain SQL applies unchanged over both psycopg and the Data API,
    • separate packages per deployable (API vs ingestion) — unnecessary indirection when both share config, DB access, and providers at this scale,
  • to achieve
    • one codebase that runs locally and deploys as both a Lambda API and an ingestion job,
    • database schema expressed as portable SQL that applies over psycopg locally and the Data API in AWS,
    • a minimal dependency footprint and fast lint/test feedback,
  • accepting
    • plain-SQL migrations lack ORM conveniences (model classes, autogenerated diffs) and must be hand-written,
    • FastAPI + Mangum incur Lambda cold starts,
    • declining an ORM means maintaining two DB access implementations (psycopg locally, RDS Data API in AWS) behind a common interface.

The backend is a single installable package with two entrypoints (API and ingestion CLI), keeping shared configuration, DB access, and Bedrock providers in one place. Schema lives as numbered .sql files that the migration runner applies idempotently; the same files will be applied to Aurora via the Data API during deployment. The repository layer will expose a small interface with a psycopg implementation now and a Data API implementation added in the AWS phase. Library choices favor a thin dependency tree to keep installs fast (notably on WSL’s /mnt/c) and Lambda packages small.

Versions are pinned as lower bounds in backend/pyproject.toml; optional groups separate api, ingestion, and dev dependencies so each runtime installs only what it needs.

The dual DB implementation (psycopg3 locally, RDS Data API in AWS) is the accepted cost of keeping Lambda outside the VPC (see RAG001). Collapsing to a single psycopg3 path in both environments would require Lambda inside the VPC, which in turn requires either a NAT Gateway ($32/month) or PrivateLink endpoints for Bedrock and Secrets Manager ($14.60/month each at 2 AZ). At demo scale the Data API abstraction is cheaper than the infrastructure it would replace.