2026-06-25 15:14:37 -03:00
2026-06-25 15:14:37 -03:00
2026-06-25 15:14:37 -03:00
2026-06-25 15:14:37 -03:00
2026-06-22 14:56:51 -03:00
2026-06-22 14:26:44 -03:00
2026-06-22 21:32:22 -03:00
2026-06-22 14:26:44 -03:00
2026-06-22 14:26:44 -03:00
2026-06-22 14:26:44 -03:00
2026-06-22 14:26:44 -03:00
2026-06-22 21:47:18 -03:00
2026-06-22 14:56:51 -03:00
2026-06-25 10:07:02 -03:00
2026-06-22 21:32:22 -03:00
2026-06-22 21:22:12 -03:00
2026-06-22 14:26:44 -03:00
2026-06-24 21:37:14 -03:00

FastAPI Skeleton

A production-shaped starting point for a web app that serves both HTML (HTMX) and a JSON API.

Stack

Concern Choice
Web framework FastAPI
HTML / interactivity Jinja2 + HTMX
ORM SQLAlchemy 2.0 (async)
Migrations Alembic
Database SQLite (default) or Postgres — one DATABASE_URL
Auth JWT access + refresh tokens, rotation, DB-backed revocation
Authorization RBAC: users → roles → permissions
Admin UI Custom HTMX dashboard (user & role CRUD)
Config env vars or .env (pydantic-settings)
Tooling uv, pytest, ruff
Packaging Dockerfile + docker-compose

Layout

app/
  core/        config, database engine/session, security (hashing + JWT)
  models/      SQLAlchemy models: user, role, permission, refresh_token
  schemas/     Pydantic request/response models
  crud/        data-access helpers
  services/    auth token issuance / rotation
  deps.py      DI: get_db, current user, require_permission(...)
  api/         JSON API: auth, users, roles
  web/         HTML/HTMX routes: pages + admin dashboard
  templates/   Jinja2 templates (+ admin partials)
  static/      CSS
alembic/       migration environment + initial migration
scripts/seed.py  bootstrap permissions, roles, first superuser
tests/         pytest (auth + RBAC)

Starting a new project

Fork or copy this repo, then update these six things before writing any code:

1. Project package name — pyproject.toml

[project]
name = "my-app"                         # used by uv/pip
description = "What your app does."

2. Application display name — .env / environment

The name shown in the browser title bar, nav bar, and /docs page title is driven by APP_NAME in your .env (or any environment variable override):

APP_NAME="My App"

This is the only runtime change needed — templates read {{ app_name }} from settings.app_name, which is sourced from this variable. No template edits required.

3. Superuser credentials — .env

FIRST_SUPERUSER_EMAIL=admin@yourapp.com
FIRST_SUPERUSER_PASSWORD=<strong-password>

4. Secret key — .env

SECRET_KEY="$(python -c 'import secrets; print(secrets.token_urlsafe(48))')"

Never commit a real secret key. The placeholder in .env.example is intentionally weak.

5. Docker / Postgres identifiers — docker-compose.yml

The compose file uses app as the Postgres username, password, and database name. Rename them consistently:

# db service
POSTGRES_USER: myapp
POSTGRES_PASSWORD: <strong-password>
POSTGRES_DB: myapp

# app service
DATABASE_URL: postgresql+asyncpg://myapp:<strong-password>@db:5432/myapp

6. This README

Replace the title and description with your project's own.


After these changes, run the normal setup:

uv sync --extra dev
cp .env.example .env   # edit the values above
uv run python -m scripts.seed
uv run uvicorn app.main:app --reload

Quick start (local, SQLite)

# 1. Install dependencies (https://docs.astral.sh/uv/)
uv sync --extra dev

# 2. Configure (optional — sensible defaults exist)
cp .env.example .env        # then edit SECRET_KEY etc.

# 3. Seed default roles/permissions + the first superuser
uv run python -m scripts.seed

# 4. Run
uv run uvicorn app.main:app --reload

Open:

Default superuser (override via env/.env): admin@example.com / changethis.

Run with Docker (Postgres)

docker compose up --build

This starts Postgres, seeds the database, and serves the app on http://localhost:8000. The app connects with DATABASE_URL=postgresql+asyncpg://app:app@db:5432/app.

Configuration

Every setting is read from an environment variable first, then .env (see .env.example). Key ones:

Variable Default Notes
SECRET_KEY dev placeholder set a strong value in production
DATABASE_URL sqlite+aiosqlite:///./app.db use postgresql+asyncpg://... for Postgres
ACCESS_TOKEN_EXPIRE_MINUTES 15
REFRESH_TOKEN_EXPIRE_DAYS 7
AUTO_CREATE_TABLES true create tables on startup (dev); use Alembic in prod
COOKIE_SECURE false set true behind HTTPS
FIRST_SUPERUSER_EMAIL / _PASSWORD admin@example.com / changethis used by seed

Auth model

  • POST /api/auth/register — self-service signup (gets the default user role)
  • POST /api/auth/login — OAuth2 password form (username = email) → access + refresh
  • POST /api/auth/refresh — rotates: the presented refresh token is revoked, a new pair is issued
  • POST /api/auth/logout — revoke one refresh token (body) or all for the user

The JSON API authenticates via Authorization: Bearer <access>. The HTMX UI stores the access token in an httpOnly cookie so pages work without client JS.

Refresh tokens are stored server-side as a SHA-256 hash + jti with a revoked flag, so logout and rotation genuinely invalidate them.

RBAC

users → roles → permissions. Permissions follow resource:action (users:read, users:write, users:delete, roles:read, roles:write). Protect an endpoint with the dependency factory:

@router.get("", dependencies=[Depends(require_permission("users:read"))])
async def list_users(...): ...

Superusers (is_superuser=True) bypass all permission checks.

Migrations (Alembic)

AUTO_CREATE_TABLES is convenient for dev/SQLite. For real deployments use migrations instead:

# apply the bundled initial migration
uv run alembic upgrade head

# after changing models, autogenerate the next revision
uv run alembic revision --autogenerate -m "describe change"
uv run alembic upgrade head

The Alembic env reads DATABASE_URL from app settings, so it targets the same database as the app.

Tests

uv run pytest

Tests run against an in-memory SQLite database and cover the auth lifecycle (register/login/me/refresh/logout) and RBAC enforcement.

Where to go next

  • Add your own models in app/models/, schemas in app/schemas/, routes in app/api/ or app/web/.
  • Add permissions to scripts/seed.py and guard routes with require_permission(...).
  • Swap HS256 for asymmetric RS256 keys if you need to verify tokens in other services.
S
Description
No description provided
Readme
1 MiB
0 Stars 1 Watchers 0 Forks
Languages
Python 81.2%
HTML 16.4%
CSS 1.8%
JavaScript 0.3%
Makefile 0.1%