Skip to content

About

No description or website provided.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

FastAPI Starter

An opinionated FastAPI backend starter: JWT authentication, PostgreSQL + Alembic, and Celery/Redis for background email. Ships with Docker Compose for local development and a pytest test suite.

Features

  • JWT bearer auth — log in with either username or email; passwords hashed with bcrypt.
  • Registration — POST /auth/register creates a user (role: admin or user).
  • Password reset — DB-backed, single-use reset tokens (SHA-256 hashed) with expiry; the reset email is dispatched to Celery so the request returns immediately.
  • Current-user endpoints — read profile, update email/username, change password, delete account.
  • SQLAlchemy 2.0 declarative models with TimestampMixin (created_at/updated_at).
  • Alembic migrations.
  • Celery worker (Redis broker) for outbound email, with Flower for monitoring.
  • HTML email templates rendered with Jinja2.
  • Docker Compose stack: app, Postgres, Redis, Redis Commander, Celery worker, Flower.
  • pytest suite running against in-memory-ish SQLite.

Tech stack

Area Choice
Web framework FastAPI (fastapi[standard])
ORM SQLAlchemy 2.0 + psycopg2-binary
Migrations Alembic
Config pydantic-settings (.env)
Auth python-jose (JWT) + passlib[bcrypt]
Task queue Celery + Redis
Task monitoring Flower
Email emails (SMTP) + Jinja2 templates
Tooling uv, Docker, pytest, ruff, mypy

Project structure

.
├── app/
│   ├── main.py                 # FastAPI app + router registration
│   ├── tasks.py                # Celery app and tasks
│   ├── utils.py                # Email rendering + sending helpers
│   ├── core/
│   │   ├── settings.py         # Pydantic settings (env-driven)
│   │   ├── db.py               # SQLAlchemy engines/sessions
│   │   ├── deps.py             # db/user dependencies, current-user parsing
│   │   └── security.py         # JWT, password hashing, reset tokens
│   ├── domain/                 # Feature modules (models, schemas, actions)
│   │   ├── auth/
│   │   │   ├── actions/        # login, register, forgot/reset password
│   │   │   ├── helpers/        # authenticate_user
│   │   │   ├── models.py       # PasswordResetToken
│   │   │   └── schemas.py
│   │   └── user/
│   │       ├── actions/        # change password, update profile
│   │       ├── enums.py        # UserRole
│   │       ├── models.py       # User
│   │       └── schemas.py
│   ├── models/
│   │   ├── __init__.py         # Base + model exports
│   │   ├── base.py
│   │   └── mixins.py           # TimestampMixin
│   ├── routers/
│   │   ├── auth.py
│   │   └── users.py
│   └── email_templates/
│       ├── src/                # Template sources (MJML)
│       └── build/              # Compiled HTML (generated; gitignored)
├── alembic/                    # Migration environment + versions
├── scripts/                    # format, lint, migrate, set-up-dev, test...
├── test/                       # pytest suite
├── .docker/                    # Dockerfile, entrypoints, compose files, nginx
├── pyproject.toml
└── docker-compose.yml          # generated by scripts/set-up-dev.sh

Requirements

  • Python >= 3.12
  • uv for dependency management
  • Docker + Docker Compose (for Postgres, Redis, Celery worker, Flower)
  • Something to compile email templates (app/email_templates/src/*.mjml → build/*.html)

Getting started

The quickest path is the setup script, which syncs deps, writes .env, brings up Docker Compose, and runs migrations:

cp .docker/docker-compose.dev.yml docker-compose.yml   # or let the script do it
chmod +x ./scripts/set-up-dev.sh
./scripts/set-up-dev.sh

Or step by step:

uv sync
cp example.dev.env .env
cp .docker/docker-compose.dev.yml docker-compose.yml
docker compose up -d --build
chmod +x ./scripts/migrate.sh && ./scripts/migrate.sh

The API is served by the app container via nginx; see the compose file for ports.

Configuration

Settings load from .env (see example.dev.env / example.prod.env) via app/core/settings.py. Unknown keys are ignored.

Variable Default Notes
ENV local local | staging | production; controls which DB host/port is used
PROJECT_NAME — App title, used in emails
SECRET_KEY random Set this. Signs JWTs
ALGORITHM HS256 JWT algorithm
ACCESS_TOKEN_EXPIRE_MINUTES 11520 8 days
FRONTEND_HOST http://localhost:3000 Included in CORS + reset links
DATABASE_NAME / DATABASE_USER / DATABASE_PASSWORD — Postgres credentials
DATABASE_HOST / DATABASE_PORT — / 6543 Used when ENV=local (host machine)
DATABASE_DOCKER_NETWORK_HOST / DATABASE_DOCKER_NETWORK_PORT — / 5432 Used when ENV!=local (inside Docker)
BACKEND_CORS_ORIGINS [] Comma-separated list
CELERY_BROKER_URI redis://localhost:6379 Redis URL for Celery
SMTP_HOST / SMTP_PORT — / 587 SMTP server
SMTP_TLS / SMTP_SSL true / false Transport security
SMTP_USER / SMTP_PASSWORD — SMTP credentials
EMAILS_FROM_EMAIL / EMAILS_FROM_NAME — / PROJECT_NAME Sender identity
EMAIL_RESET_TOKEN_EXPIRE_MINUTES 60 Reset-token lifetime

Email sending is gated by settings.emails_enabled (SMTP_HOST and EMAILS_FROM_EMAIL must both be set); otherwise send_email asserts.

API

Base path: /api/v1. Interactive docs at /docs (ReDoc at /redoc); OpenAPI at /api/v1/openapi.json.

Auth (/api/v1/auth)

Method Path Body Auth Description
POST /login/access_token form: username, password — Log in (username or email) → bearer token
POST /register CreateUserRequest — Create a user → UserResponse (201)
POST /forgot-password { "email": ... } — Queue a reset email (202)
POST /reset-password { "token": ..., "password": ... } — Set a new password

Users (/api/v1/users)

All require a bearer token.

Method Path Body Description
GET / — Current user profile
PATCH / UpdateUserRequest (email, username) Update profile (204)
PUT /change_password ChangeUserPasswordRequest Change password (204)
DELETE / — Delete current user (204)

Email templates

Templates are authored as MJML under app/email_templates/src/ and compiled to HTML in app/email_templates/build/. At runtime app/utils.py reads the built HTML and renders it with Jinja2 (render_email_template), then sends it over SMTP via the emails package.

src/*.mjml  --(build)-->  build/*.html  --(Jinja2 + context)-->  send_email() -> SMTP

app/email_templates/build/ is gitignored and must be generated. Email builders in app/utils.py: generate_password_reset_email, generate_new_account_email, generate_test_email. Only the reset email is wired up (app/tasks.py → Celery task send_password_reset_email).

Planned migration: replace the MJML sources with React Email components in a packages/react-email bun workspace, exported to app/email_templates/build/ at build time. Jinja2 stays as the runtime placeholder substituter. Not yet applied.

Database & migrations

Alembic config lives in alembic.ini and alembic/. Create and apply a migration:

./scripts/migrate.sh "add some column"   # autogenerate revision + upgrade head

app/main.py calls Base.metadata.create_all(engine) at import time, so tables are also created automatically when the app starts. Treat Alembic as the source of truth for schema changes.

Testing

Tests live in test/ and use a SQLite database with dependency overrides:

bash scripts/test.sh          # coverage run -m pytest tests/  (+ HTML report)
# or
uv run pytest test/

scripts/test.sh invokes pytest tests/ (with an s), while the suite in this repo lives in test/ — adjust the path if the runner can't find it.

Linting & formatting

./scripts/format.sh    # ruff check --fix + ruff format
./scripts/lint.sh      # mypy, ty, ruff check, ruff format --check

Background tasks

Celery uses Redis as the broker (CELERY_BROKER_URI). In Compose, the worker runs /start-celery-worker and Flower is exposed on :5557. The reset-password flow is the main producer: forgot_password_action → send_password_reset_email.apply_async().

Known rough edges

  • settings.BACKEND_CORS_ORIGINS / all_cors_origins are defined, but app/main.py does not add CORSMiddleware — CORS is currently unenforced.
  • scripts/prestart.sh and scripts/tests-start.sh reference app/backend_pre_start.py / app/tests_pre_start.py, which are not present in the repo.
  • app/initial_data.py (referenced by prestart.sh) is not present.
  • CELERY_DATABASE_URI exists for the case where the API runs on the host but Celery runs in Docker; keep the two DB host/port settings in mind.

About

No description or website provided.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages