Skip to content

tantaneity/fastapi-template

Repository files navigation

fastapi-template

Async FastAPI starter you can clone and bend into a test task in an afternoon. CRUD over a single User, but the wiring is the real point: clean layers, dependency injection, migrations, tests that actually run, and a Docker setup that comes up with one command.

Nothing fancy. Just the boring parts done right, so you don't redo them every time.

Stack

FastAPI and Uvicorn up front. SQLAlchemy 2.0 async with asyncpg underneath, Alembic for migrations, Pydantic v2 (and pydantic-settings) for schemas and config. Tests run on Pytest with pytest-asyncio and httpx. Postgres lives in Docker; the suite uses in-memory SQLite so it needs nothing running.

Layout

app/
├── api/
│   ├── deps.py            # DI wiring: session → repository → service
│   └── v1/
│       ├── router.py
│       └── routes/
│           └── users.py   # thin handlers, no logic
├── core/
│   ├── config.py          # pydantic-settings, validated at startup
│   ├── database.py        # async engine, session factory, Base
│   └── exceptions.py      # domain errors
├── models/                # SQLAlchemy models
├── schemas/               # Pydantic request/response
├── repositories/          # data access, the only place that talks to the session
├── services/              # business logic, raises domain errors
└── main.py                # app, router include, exception handlers
tests/
├── conftest.py            # test DB + httpx client fixtures
└── test_users.py
alembic/                   # env + versions

The flow is one direction: route → service → repository → DB. A handler never touches the session, a service never knows about HTTP. Swap Postgres for anything else and only database.py cares.

Run it

Docker (the easy way)

cp .env.example .env
docker compose up --build

Postgres comes up, the app waits for it to be healthy, migrations apply, then the server starts. Open http://localhost:8000/docs.

Local

You'll need a Postgres running somewhere and a .env pointing at it.

python -m venv .venv
source .venv/bin/activate          # Windows: .venv\Scripts\activate
pip install -e ".[dev]"

cp .env.example .env               # edit DATABASE_URL if needed
alembic upgrade head
uvicorn app.main:app --reload

Database

The app talks to whatever DATABASE_URL points at, nothing else to change. The driver has to be async:

postgresql+asyncpg://user:pass@host:5432/dbname   # Postgres
sqlite+aiosqlite:///./local.db                     # SQLite

Swap the URL in .env, run alembic upgrade head, done. Docker Compose already wires the app to its Postgres service. Tests are the one exception: they always spin up in-memory SQLite regardless of your .env, so the suite stays fast and self-contained.

Tests

No database needed. The suite spins up an in-memory SQLite per test and overrides the session dependency, so pytest just works.

pip install -e ".[dev]"
pytest

Migrations

After you change a model:

alembic revision --autogenerate -m "add something"
alembic upgrade head

The initial migration (0001) creates the users table, so a fresh database is one upgrade head away.

API

GET    /api/v1/users        list, skip/limit paging
POST   /api/v1/users        create, 409 on duplicate email
GET    /api/v1/users/{id}   fetch one, 404 if missing
PATCH  /api/v1/users/{id}   partial update
DELETE /api/v1/users/{id}   delete, 204
GET    /health              liveness

Full schema lives at /docs once the app is up.

Adapting for a test task

Copy models/user.py, schemas/user.py, repositories/user_repository.py, services/user_service.py, routes/users.py and rename. Each file is small and follows the same shape, so a new entity is mostly find-and-replace plus whatever logic the task actually wants. Add the model import to alembic/env.py, autogenerate, done.

About

My go-to template for most FastAPI projects: async SQLAlchemy, Alembic, Docker, tests. Clone, adapt, ship.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors