Overview โ The OpenAPI-First Mental Model
openapi-first inverts the usual FastAPI workflow. Instead of decorating routes in code and letting FastAPI invent an OpenAPI document for you, you write one OpenAPI document first and let the library assemble both the application and the client from it. The spec is the contract; code is the implementation.
๐ง 1. The Mental Model
1.1 One source of truth
Your OpenAPI document (openapi.yaml or openapi.json) is the single authoritative contract:
Every route, method, parameter, schema, and security requirement lives here โ and only here. Code never declares routes.
1.2 operationId is the binding key
The only bridge between the spec and your Python code is the operationId. Each operation maps, by name, to exactly one plain callable:
openapi_first resolves operationId: get_health โ routes.get_health and registers the route. No decorators, no @app.get, no routing metadata in code.
Guarantees this binding provides:
- ๐ซ No undocumented route can exist โ every route must be in the spec
- ๐ซ No spec operation can go unhandled โ every
operationIdmust resolve at startup - ๐ No auth can be bypassed โ security is injected from
securitySchemes+ per-operationsecurity, spec-driven - ๐งฑ No drift possible โ the server and client are built from the same document
1.3 Fail-fast by design
Contract violations are detected at application startup (or client construction), never silently at request time:
| What goes wrong | When it fails |
|---|---|
| Invalid / unloadable spec | load_openapi at startup |
| Spec fails OpenAPI 3.x validation | load_openapi at startup |
operationId missing a handler |
bind_routes at startup |
Operation declared but no operationId |
bind_routes at startup |
Missing / duplicate operationId in client |
OpenAPIClient(...) construction |
โ๏ธ 2. What It Looks Like
2.1 Scaffold an application
scaffold copies a bundled template (verbatim โ no code generation, no mutation) into a directory of your choice.
2.2 Bootstrap the server
Run with your FastAPI-compatible server (ASGI):
FastAPI itself drives the server; every route, response model, and security dependency comes from the spec. /openapi.json and Swagger UI always reflect the spec, byte-for-byte.
2.3 Talk to it with the client
OpenAPIClient builds one callable per operationId from the same spec, so the client can never drift from the server.
๐งฉ 3. Where Things Live
| Concern | Module |
|---|---|
| Load + validate spec | openapi_first.loader |
| Boot FastAPI app | openapi_first.app |
| Bind routes by opId | openapi_first.binder |
| HTTP client | openapi_first.client |
| Error hierarchy | openapi_first.errors |
| Security dependencies | openapi_first.security |
| Pydantic model codegen | openapi_first.codegen (uses datamodel_code_generator) |
| Route stub codegen | openapi_first.codegen_routes |
| Models / routes CLI | openapi_first.cli |
| Bundled templates | openapi_first.templates |
Jurisdictions:
loader,app,binder,client,errors,securityare the library API surface โ stable, tested, documented.templatesare copyable scaffolds โ not part of the library API; excluded from lint/format/type gates, never imported at runtime.
๐ 4. The Non-Goals
openapi-first deliberately does not:
- Parse decorators to generate an OpenAPI schema (that's default FastAPI behavior โ the inverse)
- Generate models from code at runtime (only at build time via CLI, from spec โ Pydantic)
- Validate request/response bodies against the spec at runtime (contract is enforced at startup / client construction; FastAPI + Pydantic handle runtime coercion)
- Invent routing from path conventions โ
operationIdbinding only - Ship a production feature set in the bundled templates (they're demos/scaffolds: in-memory stores, no concurrency, no auth configured)
๐งญ 5. Path Forward
New here? Start with 01 โ Quickstart. Want to copy a runnable app? Jump to 02 โ Templates. Digging into internals? See Design.