FastAPI Service Scaffold
When to use
Invoke when starting a new FastAPI service, standardizing an existing service baseline, modernizing a service that lacks validated settings, structured logging, health probes, or layered error handling, or preparing a FastAPI service for container deployment.
Do not use for: auth, session, or OWASP review work (use fastapi-auth-and-security-review), observability vendor wiring — OpenTelemetry, prometheus-client, SLO/alerts (use fastapi-observability-readiness), Celery/RQ/arq or Kafka task and event integration (use fastapi-async-and-task-integration), or performance and resilience gating — pool sizing, circuit breakers, load-test gates (use fastapi-performance-and-resilience).
Inputs
Required:
- Service name and the business capability it serves.
- Approved
backend-architecture.md, or explicit confirmation that backend architecture is intentionally deferred.
Optional:
- Approved
architecture/securitydecisions on auth provider, session model, and secret handling. - Data layer named by architecture (SQLAlchemy 2.x + Alembic default) — registered as a seam only, not implemented here.
- ASGI server choice if silent (Uvicorn standalone for dev; Gunicorn +
uvicorn.workers.UvicornWorkerfor prod). - Target Python version (3.11+ default), dependency manager (uv default), and monorepo vs single-service repo placement.
Operating rules
- Never generate tutorial-grade scaffolding. Assume multiple environments, structured logging, container deployment, ASGI-lifespan shutdown, and operational ownership.
- Consume
backend-architecture.md; do not invent decisions. Domain boundaries, data layer, and contracts belong tobackend-architecture.md; auth provider and secret handling belong toarchitecture/security. If either is silent on a decision this scaffold needs, pause and raise an ADR candidate rather than guessing. - Owns the DI and principal-provider baseline only — the shell, not the flow. Token verification, session, and protected-route logic belong to
fastapi-auth-and-security-review. Scaffold the seam (a typedDepends); do not implement the flow. - Secure by default: settings are validated at boot and fail fast; no secrets in source, committed config, the image, or
.env; the container runs as a non-root user from a digest-pinned base image. - Settings are validated, not read ad hoc: a single typed Pydantic
Settings(BaseSettings) instance built at startup. Missing or malformed required variables abort boot with a clear message — never a runtimeNone. - Error handling is layered in a specific order:
sys.excepthookand the asyncio loop exception handler (log fatal, flush, exit non-zero for unhandled) → FastAPI/Starlette exception handlers (structured body, no traceback or internals in non-dev) → ASGI-lifespan graceful shutdown (stop accepting, drain in-flight, close registered resources within a bounded timeout) wired to uvicorn signal handling. All four are required. - Observability is a seam, not an implementation: a structlog structured logger bound to a
contextvarsrequest context (request id correlated) is mandatory in the baseline; OpenTelemetry, metrics, and alerting are explicitly deferred tofastapi-observability-readinessbehind a no-op tracer/metrics interface. - Health probes are mandatory: a liveness endpoint (process is up) and a readiness endpoint (registered dependency checks pass) are separate and wired before any feature route.
- A scaffold that does not run is not done. Run typecheck, lint, the test run, and a boot smoke check before declaring completion; fix and re-run on failure.
- Confirm the target directory before writing files. Recommend
services/<service-name>/in a monorepo or repo root for a single-service repo. Refuse to write into a plugin or skill repository without explicit user override.
Output contract
The generated service shell MUST conform to:
- deployment-standards — env-agnostic image; runtime config injected, not baked; one artifact per environment; non-root container.
- observability-standards — structured logging with request-id correlation and an environment tag; tracer/metrics seam present.
- security-standards — no secrets in source, committed config, or image; settings fail fast on missing required secrets.
- naming-conventions — service, module, and file naming.
Upstream contract: backend-architecture.md is the source of truth for domain boundaries, data layer, and contracts; architecture/security is the source of truth for auth provider, session model, and secret handling. If either is silent on a decision this scaffold needs, pause and raise an ADR candidate rather than guessing.
Progressive references
- Read
references/fastapi-scaffold-playbook.mdwhen implementing any owned area or checking the anti-pattern list. - Read
references/fastapi-scaffold-quality-rubric.mdbefore declaring the scaffold complete. - Use
assets/fastapi-service-scaffold.template.mdas the directory-layout, settings, and lifespan reference.
Process
- Gather context: service name, business capability, target directory, target Python version, dependency manager, and the auth provider decision from
architecture/security. Loadbackend-architecture.mdand extract domain boundaries, data layer, and contracts. If a needed decision is missing, raise an ADR candidate before proceeding. Confirm the target directory is not a plugin or skill repository. - Generate the project layout:
pyproject.toml(deps pinned exactly with a hashed lockfile — no unbounded specifiers,requires-pythonset),mypy/ruffconfig,.gitignore, and the package structure (domain layout perbackend-architecture.md). Referenceassets/fastapi-service-scaffold.template.mdfor the canonical layout. - Generate validated settings: a Pydantic
Settings(BaseSettings)inapp/config.pyparsing the environment at import, instantiated once; abort with a clear message on any missing or malformed required variable. Create.env.exampledocumenting every variable with placeholder values only. - Generate the structured logging and request-context seam: a structlog logger in
app/observability/logging.py; acontextvarsrequest context inapp/observability/context.py; ASGI middleware that assigns or propagates a request id and binds a bound logger. Add a no-op tracer/metrics interface inapp/observability/telemetry.pywith explicit TODO comments namingfastapi-observability-readinessas the owner. - Generate the layered error handling:
sys.excepthookandloop.set_exception_handler(log fatal, flush, exit non-zero) inapp/server/process_handlers.py; FastAPI/Starlette exception handlers returning a structured body with no traceback or internals in non-dev; an ASGI-lifespan (lifespancontext manager) that on shutdown stops accepting, drains in-flight, and closes registered resource hooks within a bounded timeout. - Generate health probes: a liveness endpoint (
/healthz) returning 200 when the process is up, and a readiness endpoint (/readyz) iterating registered dependency-check callables and returning 503 until all pass. Wire both before any feature router. - Generate the DI and principal-provider baseline: FastAPI
Dependsproviders inapp/container.py. Register the principal/auth-context provider as a typed dependency shell. Add explicit TODO comments namingfastapi-auth-and-security-reviewas the owner of token verification, session, and protected-route logic, and the data-layer client (SQLAlchemy session) as a seam owned by the data implementation. - Generate container packaging: a multi-stage
Dockerfile(build stage, slim runtime stage) running as a non-root user from a digest-pinned base image, a.dockerignore, and a documentedHEALTHCHECKhitting/healthz. No secrets baked; runtime config arrives via environment. - Generate local-run documentation in the service README: how to run each environment, every required environment variable (mirrored from
.env.example), the runtime-config contract, and the explicit table of seams downstream archetypes fill (auth, observability vendor, task/event, performance gates, data layer). - Build verification (mandatory): run
mypy,ruff check, the test command (pytest), and a boot smoke check (start the app, asserthttpxGET /healthzreturns 200, shut down cleanly via lifespan). Fix and re-run on failure. Then validate against the standards in the Output contract; revise until all pass or explicitly document any unresolved gap in the README.
Outputs
Required:
- Python/FastAPI service package with
pyproject.toml(pinned deps + hashed lockfile,requires-python) andmypy/ruffconfig. - Validated Pydantic
Settingswith fail-fast boot and.env.example. - structlog logger +
contextvarsrequest context + request-id correlation; no-op tracer/metrics seam. - Layered error handling: process/asyncio handlers, FastAPI/Starlette exception handlers, ASGI-lifespan graceful shutdown (all wired).
- Separate liveness (
/healthz) and readiness (/readyz) probes. DependsDI providers with a typed principal-provider shell (auth flow explicitly deferred).- Multi-stage non-root
Dockerfilefrom a digest-pinned base,.dockerignore, documentedHEALTHCHECK. - Service README listing all seams downstream archetypes fill.
Output rules:
- Generated files are functional and the app boots, not placeholder-heavy.
- No secrets in source, committed config,
.env.example, or the image. - The principal provider is a seam, not a flow — token verification, session, and route guards are explicitly deferred to
fastapi-auth-and-security-review. - The same image runs in every environment; configuration arrives at runtime via environment variables.
Quality checks
-
mypyandruff checkreport zero errors. - The test command runs and the boot smoke check (
GET /healthz→ 200, clean lifespan shutdown) passes, or a skip is documented with reason. -
pyproject.tomlpins every dependency with a hashed lockfile and setsrequires-python. - Settings are validated at boot and abort on a missing required variable — verified by removing one required var and observing a clear non-zero exit.
- No secrets appear in source, committed config,
.env.example, or the built image. - All four error layers are wired:
sys.excepthook, asyncio loop handler, FastAPI/Starlette exception handlers, ASGI-lifespan graceful shutdown. - Exception handlers return no traceback or internal details in non-dev environments.
-
/healthzand/readyzare separate;/readyzreturns 503 until registered dependency checks pass. - The structlog logger binds a request id from the
contextvarscontext; the tracer/metrics seam is a no-op with a TODO namingfastapi-observability-readiness. - The
Dependsprincipal provider is a typed shell tied to anarchitecture/securitydecision; token/session logic is explicitly deferred with TODO comments. - The
Dockerfileis multi-stage, runs as non-root, uses a digest-pinned base, and bakes no secrets. - The service README lists all seams downstream archetypes are expected to fill.
References
- Upstream:
architecture/backend-architecture,architecture/security. - Related FastAPI archetype skills (extend this baseline):
fastapi-auth-and-security-review,fastapi-observability-readiness,fastapi-async-and-task-integration,fastapi-performance-and-resilience. - Standards:
deployment-standards,observability-standards,security-standards,naming-conventions.