Skip to content

piewared/api-forge

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

285 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

API Forge — a FastAPI template that's actually opinionated

Python License: MIT Lint Types Tests

A production-shaped FastAPI template that ships the boring decisions already made. OIDC + BFF authentication, type-safe persistence, durable workflows, clean-architecture scaffolding, and your choice of deployment target — all behind one CLI.

copier copy --trust gh:piewared/api-forge my-service
cd my-service && uv sync
uv run api-forge-cli dev up        # full stack in Docker, hot reload

Open http://localhost:8000/docs and you have a running, authenticated, type-checked API.


What you actually get

A scaffold that encodes good patterns. One CLI command produces a fully wired entity — domain model, persistence model, repository, request/response DTOs, application service with proper transaction boundaries, and an HTTP router that's auto-discovered at startup. No edits to app.py ever.

api-forge-cli entity add Order
# → entities/service/order/{entity, table, repository, schemas, service, router}.py
# → endpoints live at /api/v1/orders/ — no wiring required

BFF authentication that's not boilerplate. OIDC Authorization Code + PKCE with server-side sessions, HttpOnly signed cookies, CSRF protection, client fingerprinting, and JWKS-based token validation. Pre-seeded Keycloak in dev, managed IdP (Google / Microsoft / Okta / Auth0 / Cognito) in prod via config. The hard parts have already been gotten wrong by someone else.

Durable workflows when you need them. Temporal scaffolding with typed input/result, auto-discovered workflow + activity registry, and the canonical "endpoint → service → workflow start" pattern wired in one command:

api-forge-cli entity add Order --with-workflow OrderDispatch
# → also generates the workflow module and adds an async dispatch()
#   method to OrderService that starts it with idempotent IDs

Pick your deployment target. Docker Compose for prod, Kubernetes via Helm, or Fly.io. Each is an opt-in toggle at template-generation time — if you don't need k8s, the Helm machinery never lands in your repo.

Dev/prod parity by default. The dev stack is the prod stack: same PostgreSQL, same Redis, same Temporal, same Keycloak (dev only) — all in Docker Compose, started with one command.


Quick start

1. Generate a project

uv tool install copier         # or: pip install -U copier
copier copy --trust gh:piewared/api-forge my-service
cd my-service

Copier asks a handful of questions. The defaults give you a slim project (no fly, no k8s); opt in to deployment targets you actually use:

Question Default What it controls
use_redis yes Redis caching, sessions, rate limiting
use_temporal yes Temporal workflow scaffolding + worker
use_postgres no Postgres deps + URL (default is SQLite for fast start)
include_fly_deploy no api-forge-cli fly command + Fly.io infra adapter
include_k8s_deploy no api-forge-cli k8s command + Helm deployer + manifests

Toggling these off doesn't comment-out code — entire subtrees are excluded from the generated project. You only carry what you use.

⚠️ Copier needs --trust for templates with post-generation hooks. The hooks are open source — see copier.yml and scripts/post_gen_setup.py.

2. Install + run

uv sync
cp .env.example .env
uv run api-forge-cli dev up

The dev stack pulls and runs PostgreSQL, Redis, Temporal, Keycloak, and the API itself in Docker. First run takes a minute or two; subsequent starts are seconds.

Once healthy:

What URL
API http://localhost:8000
Interactive docs http://localhost:8000/docs
Keycloak (dev only) http://localhost:8080   (admin / admin)
Temporal UI http://localhost:8082

3. Iterate

api-forge-cli dev logs api      # tail just the API
api-forge-cli dev restart api   # restart after dependency changes
api-forge-cli dev down          # stop everything

4. Update later

api-forge-cli update    # pull template improvements; merges with your changes

Use api-forge-cli update rather than copier update directly. The template renames src/<package_name>/ after generation, which Copier doesn't know about; the wrapper handles the rename dance so Copier's three-way merge sees a tree shaped the way it expects, then restores the package layout afterwards. Net effect: template changes land as staged-but-uncommitted edits ready for git diff --staged and your usual review.


CLI tour

Everything lives under api-forge-cli. Subcommands group by concern:

api-forge-cli --help
dev        Development environment commands
prod       Production Docker Compose commands
k8s        Kubernetes Helm deployment commands       (toggle: include_k8s_deploy)
fly        Fly.io deployment commands                (toggle: include_fly_deploy)
config     Configuration validation
entity     Entity scaffolding (add / rm / ls)
workflow   Temporal workflow scaffolding             (when use_temporal=true)
activity   Temporal activity scaffolding             (when use_temporal=true)
secrets    Secret generation and management
users      Keycloak user management (dev)

Scaffolding

# CRUD entity — entity / table / repo / schemas / service / router + tests
api-forge-cli entity add Product

# Entity + Temporal workflow, fully wired:
# - service.py auto-imports TemporalClientService and stores it
# - router.py injects the temporal dep via FastAPI
# - service.dispatch(<id>) starts the workflow with an idempotent ID
api-forge-cli entity add Order --with-workflow OrderDispatch

# Standalone workflow / activity — when the work spans multiple entities,
# is scheduled, or isn't tied to a specific entity's lifecycle. Pick an
# orchestrator entity and wire dispatch() there manually.
api-forge-cli workflow add OrderFulfillment   # spans Order + Inventory + Shipment
api-forge-cli activity add send_welcome_email

Iteration

api-forge-cli dev up              # local Docker Compose, hot reload
api-forge-cli fly sync            # push current code to Fly main app (fast path)
api-forge-cli fly up              # full Fly stack (services + main app)
api-forge-cli k8s up              # deploy via Helm

fly sync is the tight code-iteration loop: skips the supporting-services phase and pre-flight, just builds and ships the main app image. fly up is the full reconcile (Redis + Temporal + Postgres + main app).


Architecture, briefly

HTTP request
   │
   ▼  router.py        — thin handler: delegates to service
   ▼  service.py       — owns transactions; raises domain errors
   ▼  repository.py    — CRUD against the table
   ▼  table.py         — SQLModel persistence
   ▼  entity.py        — Pydantic domain model with invariants

Services own commit/rollback. Routers translate domain errors to HTTP status codes. Entities never import SQLModel (persistence stays at the edge). Each new entity follows this shape because the scaffold encodes it. Deep dive →

For background work, two layers of choice:

Need Use
Durable, retryable, replayable Temporal (scaffolded)
Fire-and-forget after the response FastAPI BackgroundTasks

The architecture doc explains the asymmetry and shows the canonical patterns for both.


Authentication

OIDC Authorization Code + PKCE with server-side sessions:

  • All web auth endpoints under /auth/web (login, callback, me, refresh, logout).
  • HttpOnly + signed session cookies; CSRF token rotated on refresh.
  • redirect_uri is server-configured — never trusted from the client.
  • Client fingerprinting binds sessions to the user agent.
  • JWKS-based JWT validation for non-cookie clients (mobile, service-to- service).

Dev: pre-seeded Keycloak realm. Prod: configure any managed IdP (Azure AD, Okta, Auth0, Google, Cognito) by setting oidc.providers in config.yaml.

Auth deep dive →  ·  Sessions & cookies →


Configuration

config.yaml is the single source of truth, with environment variable substitution everywhere:

database:
  url: "${DATABASE_URL:-sqlite:///./database.db}"
redis:
  enabled: true
  url: "${REDIS_URL:-redis://localhost:6379}"
temporal:
  enabled: true
  url: "${TEMPORAL_URL:-temporal:7233}"
oidc:
  providers:
    google:
      issuer: "${OIDC_GOOGLE_ISSUER:-https://accounts.google.com}"
      client_id: "${OIDC_GOOGLE_CLIENT_ID}"
      client_secret: "${OIDC_GOOGLE_CLIENT_SECRET}"

.env provides per-environment values. Pydantic models enforce the shape at startup. Service feature flags (redis.enabled, temporal.enabled) are honoured at runtime — disable Temporal and the workflow scaffolds refuse to run, the worker exits cleanly, and rate-limiting falls back to in-memory.

Configuration reference →


Testing

uv run pytest                  # everything
uv run pytest tests/unit/      # unit only — fast, no infra
uv run pytest tests/integration/   # needs dev stack running
uv run pytest tests/e2e/       # full auth + workflow paths

Testing strategy →


Deployment

Three first-class targets, each opt-in via copier:

Target Command When
Docker Compose api-forge-cli prod up Single host, simple ops
Kubernetes api-forge-cli k8s up Multi-node, autoscaling, your cluster
Fly.io api-forge-cli fly up Managed multi-region, no ops

For Fly, fly sync is the dev loop — fast iteration on the main app without touching supporting services.


Project structure

my-service/
├── my_service/                       # Your application package
│   ├── app/
│   │   ├── api/http/                 # FastAPI factory, middleware, routers
│   │   │   ├── app.py                # Slim create_app() factory
│   │   │   ├── lifespan.py           # Startup/shutdown sequencing
│   │   │   ├── deps.py               # Cross-cutting FastAPI dependencies
│   │   │   └── routers/              # Auth + auto-discovery loader
│   │   ├── core/services/            # Cross-cutting infra (JWT, OIDC, Redis, …)
│   │   ├── entities/                 # Domain entities (CLI scaffolds here)
│   │   │   ├── core/                 # Auth-essential (User, UserIdentity)
│   │   │   └── service/              # Generated CRUD entities
│   │   │       └── <name>/{entity, table, repository, schemas, service, router}.py
│   │   ├── runtime/                  # Config loading, DB init
│   │   └── worker/                   # Temporal workflows + activities
│   └── cli/                          # api-forge-cli commands
├── tests/                            # Unit, integration, e2e
├── docs/                             # Architecture, auth, deployment guides
├── docker-compose.dev.yml            # Dev stack
├── docker-compose.prod.yml           # Prod-shaped Compose stack
├── config.yaml                       # Single-source config with env substitution
└── pyproject.toml

Who this is for

A good fit if:

  • You're building a backend serving web/SPA clients and want OIDC sessions done correctly instead of rolling your own.
  • You want production-shaped local dev — same Postgres, same Redis, same Temporal — without a full afternoon of setup.
  • You want a scaffold that encodes architectural decisions, so a feature is one CLI command instead of seven files of boilerplate.

Probably not a fit if:

  • You want a minimal "hello world" REST API with zero infra.
  • You don't want Docker in your workflow.
  • You're committed to a stack the template doesn't speak (Django, Flask + SQL Alchemy classic, etc.).

Documentation


Requirements

  • Python 3.13+
  • Docker & Docker Compose
  • uv (recommended) or pip + virtualenv
  • Helm v3 + kubectl if generating with include_k8s_deploy=yes
  • fly CLI if generating with include_fly_deploy=yes

License & support

MIT — see LICENSE.

Bugs and feature requests via GitHub Issues. Questions and ideas via Discussions.

About

A production-ready FastAPI template for building scalable Python APIs with PostgreSQL, SQLModel, Redis, and Temporal—fully configured for local and cloud deployment.

Topics

Resources

License

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors