Course outline

FastAPI / Intermediate: application integration and security / Lesson 9

Routers, OpenAPI, and contract evolution

Organize operations into explicit modules and verify generated schema as part of a stable public API rather than treating documentation as authorization.

About 5 min readContent reviewed
On this page

Lesson goals

  • Explain the assumptions and boundaries of routers, openapi, and contract evolution.
  • Interpret both complete examples and justify their observed outputs.
  • Diagnose the listed failures and apply the worked solutions while keeping identity, state, and lifecycle ownership explicit.

Compose routes without starting the world

APIRouter groups related operations that can be included in an application with deliberate prefixes, dependencies, tags, and responses. Importing a router module should not unexpectedly connect to production storage, start workers, or mutate global infrastructure. The first example includes a versioned item router and tests the resulting path. A prefix is a routing choice, not automatic backward compatibility. Keep application construction separate from process startup and inject resources through explicit providers or lifespan. Small modules help review when they express responsibilities; dividing one operation across many opaque files can make its security and transaction behavior harder to understand.

python Complete router and operation-ID check
from fastapi import APIRouter, FastAPI
from fastapi.testclient import TestClient
router = APIRouter(prefix="/v1/items", tags=["items"])
@router.get("/{item_id}", operation_id="read_item_v1")
def read_item(item_id: int): return {"id": item_id}
app = FastAPI()
app.include_router(router)
with TestClient(app) as client:
    print(client.get("/v1/items/4").json())
print(app.openapi()["paths"]["/v1/items/{item_id}"]["get"]["operationId"])
Expected output
{'id': 4}
read_item_v1

The included prefix yields the public route and the chosen operation ID. Router tags change documentation grouping, not permission.

Read generated OpenAPI as a declared contract

FastAPI generates an OpenAPI document from supported operation, parameter, model, and security declarations. It can describe required input, response schemas, status codes, and authentication mechanisms. The second example inspects the public response reference and required fields without running a server. This is useful evidence of the declaration, but schema cannot prove that a permission check runs or that database writes are atomic. Compare schema with real request tests. Changes to aliases, strictness, unknown-field policy, response shape, and validation bounds can affect clients even when the Python function name remains unchanged.

python Complete in-memory response-schema check
from fastapi import FastAPI
from pydantic import BaseModel
class PublicItem(BaseModel):
    id: int
    name: str
app = FastAPI()
@app.get("/item", response_model=PublicItem)
def item(): return {"id": 1, "name": "pen"}
schema = app.openapi()
reference = schema["paths"]["/item"]["get"]["responses"]["200"]["content"]["application/json"]["schema"]["$ref"]
print(reference.endswith("/PublicItem"))
print(schema["components"]["schemas"]["PublicItem"]["required"])
Expected output
True
['id', 'name']

The response references PublicItem and requires id and name. Schema inspection makes no HTTP request and does not verify the handler’s business semantics.

Keep operation identity and errors predictable

Client generators and integrations can depend on operation IDs, parameter names, and response definitions. Choose unique stable operation IDs where that is part of the contract and test them. Tags organize documentation and are not access controls. Declared additional responses describe possibilities but do not automatically implement those branches. If a custom error handler changes validation shape, document and verify that public result rather than leaving the generated default as the only promise. Avoid exposing internal model names or privileged fields accidentally through schema composition, and review whether documentation endpoints need a particular access policy in the deployment.

Evaluate compatibility from the client’s view

Adding a newly required field, narrowing an accepted range, removing an output field, or changing authentication can be breaking behavior. A new optional response property may still affect strict consumers, so understand actual client contracts instead of relying on a slogan. Use explicit version or migration policy, deprecation communication, and mixed-version data review where needed. Keeping /v1 in the path does not justify silently changing what /v1 accepts. Schema diffs can identify a candidate change, but semantic and authorization regressions need targeted behavior tests and evidence from the affected journey.

Treat documentation output as versioned evidence

Record the dependency baseline, schema, relevant tests, and release identity together. Inspect a concise schema diff and verify representative accepted and rejected requests before promotion. Disable or configure documentation only for an explicit requirement, not as a substitute for real authentication or network policy. The examples use isolated applications and generated in-memory documents; they fetch no external schema or client generator and open no server. A usable API is discoverable and predictable under its contract, while internal implementation detail remains hidden unless clients need it to make a meaningful decision.

Common errors and fixes

A documented error is assumed implemented

Why it happens
Response metadata was confused with handler logic.
How to fix it
Implement and test the real failure branch.
Prevent it next time
Compare schema declarations with request behavior.

A route prefix is treated as compatibility proof

Why it happens
Version naming replaced actual input and output review.
How to fix it
Review affected client contracts and migration behavior.
Prevent it next time
Use schema diffs and targeted semantic tests together.

Router import connects to a real service

Why it happens
Module side effects hid resource startup.
How to fix it
Move resource acquisition into an explicit owned lifecycle.
Prevent it next time
Test app construction without configured services.

Check your understanding

1. A field becomes required but the URL still says /v1. Is that automatically compatible?

Show worked solution

No. Existing clients that omit the field can now fail validation. Review the migration and version policy, test old request shapes, and communicate the changed contract. A path label cannot preserve behavior that the implementation changed.

2. Why should operation IDs be tested if clients are generated from OpenAPI?

Show worked solution

Generated method names or integrations can depend on those identifiers. Accidental renaming or duplication can break tooling even if the HTTP path still responds. Treat the selected identifiers as an explicit public integration contract when clients rely on them.

Key takeaways

  • APIRouter groups related operations that can be included in an application with deliberate prefixes, dependencies, tags, and responses.
  • Client generators and integrations can depend on operation IDs, parameter names, and response definitions.
  • Record the dependency baseline, schema, relevant tests, and release identity together.

Official references