A production-ready FastAPI template with postgres, JWT auth, structured exceptions, and a worked auth/users example.
- FastAPI 0.115 / Starlette
- SQLAlchemy 2.0 (async) + asyncpg + Alembic
- Pydantic 2 / pydantic-settings
- PyJWT + passlib[bcrypt]
- fastapi-pagination, prometheus-fastapi-instrumentator, sentry-sdk
- APScheduler (lifespan-driven)
- uv + ruff + pre-commit
src/
βββ api/ # routers, dependencies, custom middlewares, exception handlers
βββ core/ # settings, JWT config, postgres config, logging, timezone
βββ db/ # async engine, session factory, mixins, declarative base
βββ enums/
βββ exceptions/
β βββ repositories/
β βββ services/ # raised by services; auto-translated to JSON 4xx/5xx
βββ integrations/
βββ jobs/lifespan.py # APScheduler entry; register cron jobs here
βββ managers/db/ # TransactionManager / ReadonlyManager β wire repos here
βββ mappers/ # ORM β Pydantic translation
βββ migrations/ # Alembic
βββ models/ # SQLAlchemy ORM models
βββ repositories/ # CRUD on models, returns mapped entities
βββ schemas/ # Pydantic request/response schemas
βββ security/ # JWT + bcrypt (interfaces + implementations)
βββ services/ # business logic (BaseService[TM])
βββ tests/
git clone <fork>
cd fastapi-microservice
cp infra/.env.example infra/.env
# edit infra/.env with your local DB_* and JWT_SECRET_KEY
# bring up postgres
docker compose -f infra/docker-compose.yaml up -d postgres
uv sync
uv run alembic upgrade head
uv run uvicorn src.main:app --reloadOpen http://localhost:8000/template/docs (basic auth: admin / admin β see Settings.docs_password).
POST /template/api/v1/auth/registerβ create a user, returns access + refresh tokensPOST /template/api/v1/auth/loginβ exchange credentials for tokensPOST /template/api/v1/auth/refreshβ exchange a refresh token for a new pairGET /template/api/v1/auth/meβ JWT payload of the current userGET /template/api/v1/usersβ paginated list (auth required)GET /template/api/v1/users/{id}POST /template/api/v1/usersPATCH /template/api/v1/users/{id}DELETE /template/api/v1/users/{id}GET /template/healthGET /template/readyGET /template/metrics(Prometheus)
Before exposing this template publicly, change the defaults:
JWT_SECRET_KEYβ generate withopenssl rand -hex 32. The defaultchange-me-in-prodis unsafe and PyJWT will warn.CORS_ALLOW_ORIGINSβ set to the explicit list of front-end origins (e.g.["https://app.example.com"]). The default["*"]is for local dev only.DOCS_USERNAME/DOCS_PASSWORDβ change or disable the basic-auth-protected/template/docsendpoint.DB_PASSWORDβ pick a strong password and inject via secrets manager, notinfra/.env.SENTRY_DSNβ optional but recommended; sentry integration is wired insrc/main.py.
- Find/replace
/template/with/<your-service>/acrosssrc/main.pyandsrc/api/v1/__init__.py. - Rename in
pyproject.toml:[project] name,[tool.hatch.build.targets.wheel] packages(already["src"]). - Add domain models, schemas, mappers, repositories, services, routers β follow the
usersexample. - Wire new repositories in
src/managers/db/transaction.pyandreadonly.py. - Register your routers in
src/api/v1/__init__.py.
uv run pytest src/tests/ -vMIT