ブログ一覧へ
バックエンド

FastAPI: Type Hints as Infrastructure

One idea — the function signature is the contract — gives you validation, serialization, docs, and editor support at once. Here is how it works and where it bites.

公開日
読了時間
約10分
著者
Yakhya

FastAPI's whole design follows from a single bet: that a Python type hint is enough information to build everything else from. Declare that a parameter is a `User`, and the framework derives request parsing, validation, error responses, serialization, and an OpenAPI schema from that one declaration. Nothing is duplicated, so nothing can drift — the documentation is generated from the code that runs, not from a comment above it.

The core loop

python
from fastapi import FastAPI, Depends, HTTPException, status
from pydantic import BaseModel, Field

app = FastAPI(title="Payments API", version="1.0.0")

class PaymentIn(BaseModel):
    account_id: str
    amount_minor: int = Field(gt=0)      # validated, documented, enforced
    currency: str = Field(min_length=3, max_length=3)

class PaymentOut(PaymentIn):
    id: str
    status: str

@app.post("/payments", response_model=PaymentOut, status_code=201)
async def create_payment(
    payload: PaymentIn,
    db: Session = Depends(get_db),
) -> PaymentOut:
    if not await account_exists(db, payload.account_id):
        raise HTTPException(status.HTTP_404_NOT_FOUND, "account not found")
    return await payments.create(db, payload)

That handler already rejects a negative amount with a structured 422, refuses a four-letter currency, strips fields the response model does not declare, and appears in a working Swagger UI at /docs. There is no serializer class, no schema file, and no decorator soup — the signature did all of it.

Dependency injection is the feature people underrate

`Depends` is a small, honest DI container. A dependency is just a function; it can itself have dependencies, it can be async, it can yield (giving you setup and teardown around the request), and it is cached per request so ten handlers asking for the current user resolve it once. Auth, database sessions, pagination parameters, feature flags, and tenant resolution all become composable functions rather than middleware that mutates a request object.

  • Yield-based dependencies give you clean transaction scoping: open a session, yield it, commit or roll back after the response.
  • `app.dependency_overrides` swaps any dependency in tests — the single best reason FastAPI test suites stay fast and hermetic.
  • Router-level and app-level dependencies apply a check to a whole group of routes without repeating it on each handler.
  • Because dependencies are ordinary functions, they are unit-testable on their own, which middleware rarely is.

ASGI, and the async you have to take seriously

FastAPI is a thin layer over Starlette, which is an ASGI framework — so you get WebSockets, background tasks, streaming responses, and genuine concurrency for I/O-bound work. The catch is the one every async runtime has: a blocking call inside an `async def` handler stalls the entire event loop, not just that request. A synchronous database driver, `requests`, `time.sleep`, a heavy Pandas operation — any of these turn a concurrent server into a serial one.

The rule: if the function body is fully async, use `async def`. If any part of it blocks, use plain `def` and let FastAPI run it in a threadpool. Mixing the two is where FastAPI performance complaints come from.

Production notes

  1. 01Run Uvicorn workers under Gunicorn, or Uvicorn directly with a process manager — one process per core, as with any Python service.
  2. 02Use async drivers end to end (asyncpg, SQLAlchemy 2.0 async, httpx) or commit to sync handlers. Half-and-half is the worst of both.
  3. 03Pydantic v2 moved validation into Rust and is several times faster than v1 — worth the migration if you are still on the old one.
  4. 04Separate input, output, and database models. Reusing one class for all three is how private fields end up in a public response.
  5. 05Register exception handlers so every error — validation, domain, unexpected — returns the same JSON shape.
  6. 06Add lifespan handlers for connection pools and warmup rather than module-level globals.
  7. 07Export the generated OpenAPI schema in CI and diff it; a breaking API change becomes a reviewable file change.

When it is the right choice

FastAPI is the strongest option in Python for JSON APIs, and it is close to unbeatable for serving machine learning models — the ecosystem you need is already Python, and the framework adds typed contracts and async serving on top. Choose Django instead when you want the admin, the ORM conventions, and a batteries-included path for a content-heavy product. Choose Flask when you want minimal ceremony and no async at all. And remember what FastAPI does not give you: no ORM, no migrations, no admin, no opinionated project layout. It is a well-designed API layer, and the rest of the architecture is still yours to decide.

タグ
FastAPIPythonPydanticASGIAPI Design
続けて読む記事一覧