Cloud Parts

FastAPI

A Python API framework built on Starlette and Pydantic: declare parameters and models with type hints, and it validates requests, serializes responses and generates an OpenAPI document with interactive docs.

What it is
API frameworks (http api frameworks)
Type
Open source
Made by
Sebastián Ramírez (tiangolo) and contributors
License
MIT
Facts checked
9 October 2026

What it is

FastAPI is Starlette with an API layer on top. The FastAPI class subclasses Starlette, so routing, middleware, WebSockets and the ASGI interface are Starlette’s, and an ASGI server such as Uvicorn runs it. What FastAPI adds is reading your function signatures. A path operation is a function under a decorator such as @app.get("/items/{item_id}"), and its parameters say where each value comes from (path, query string, header, cookie, or a JSON body declared as a Pydantic model) and what type it must be. FastAPI reads the body as JSON, converts and validates every value with Pydantic, and answers 422 with the location of each error before your function runs. The return value passes through the declared return type or response_model, which validates it and filters out any field the model doesn’t list. The same declarations become an OpenAPI document with a JSON Schema for every model, served as Swagger UI at /docs and ReDoc at /redoc.

Dependencies are plain functions passed to Depends(). FastAPI calls them before the endpoint and injects their results, and dependencies can have dependencies of their own; that is how authentication, database sessions and shared parameters are wired. Endpoints and dependencies can be async def, which run on the event loop, or plain def, which FastAPI runs in a thread pool so they don’t block the server. GraphQL libraries plug in through ASGI. The docs recommend Strawberry, whose GraphQLRouter is a FastAPI router, so REST and GraphQL endpoints share one app and the same dependencies. FastAPI is still at 0.x (0.143.0 came out on October 8, 2026), and its docs say a new minor version may contain breaking changes, so pin the version you use.

Why teams pick it

  • One type declaration gives parsing, conversion, validation, editor completion and API docs, with no separate schema language to keep in step
  • OpenAPI and JSON Schema generated from the code, with Swagger UI and ReDoc built in and client generators able to read the same document
  • Dependency injection with sub-dependencies and no registration step, used for auth, database sessions and GraphQL context alike
  • Everything Starlette has (WebSockets, background tasks, mounts, a test client), and any ASGI library, such as a GraphQL server, can sit beside your routes

Watch out for

  • Still 0.x after nearly eight years: minor releases may break things, so pin the version and read the release notes before upgrading
  • Tied to Pydantic: moving to Pydantic 2 needed model changes, and 0.126.0 (December 2025) dropped Pydantic 1 support
  • Plain def endpoints and dependencies share Starlette’s thread pool, 40 threads by default; blocking I/O inside async def holds up every request on that worker
  • No database layer, admin or migrations of its own: the SQL tutorial brings in SQLModel, which is built on SQLAlchemy and Pydantic
  • Validation and serialization cost CPU on every request, and FastAPI can’t be faster than the Starlette layer it runs on

How it fits with other technologies

A path operation with a dependency and a response model · python

# pip install fastapi httpx2   (serve it for real with: fastapi dev main.py)
from typing import Annotated
from fastapi import Depends, FastAPI, Header, HTTPException
from fastapi.testclient import TestClient
from pydantic import BaseModel

class ItemOut(BaseModel):
    id: int
    name: str

ITEMS = {1: {"id": 1, "name": "Lamp", "supplier_cost": 12.5}}
app = FastAPI()

def current_user(x_token: Annotated[str, Header()]) -> str:  # a dependency
    if x_token != "secret":
        raise HTTPException(status_code=401, detail="bad token")
    return "ada"

@app.get("/items/{item_id}", response_model=ItemOut)
async def read_item(item_id: int, user: Annotated[str, Depends(current_user)]):
    if item_id not in ITEMS:
        raise HTTPException(status_code=404, detail="no such item")
    return ITEMS[item_id]  # supplier_cost is filtered out by ItemOut

client = TestClient(app)
print(client.get("/items/1", headers={"x-token": "secret"}).json())  # {'id': 1, 'name': 'Lamp'}
print(client.get("/items/1", headers={"x-token": "nope"}).status_code)  # 401
print(client.get("/items/one", headers={"x-token": "secret"}).status_code)  # 422

GraphQL beside REST: FastAPI, Strawberry and SQLAlchemy#

The stack from the Python GraphQL API architecture, small enough to run. FastAPI routes /graphql to Strawberry’s router, Strawberry hands the query to graphql-core, and the resolver loads rows through an async SQLAlchemy session that a FastAPI dependency opens per request. SQLite stands in for PostgreSQL so it runs anywhere; only the engine URL changes.

python
# pip install fastapi "strawberry-graphql[fastapi]" "sqlalchemy[asyncio]" aiosqlite httpx2
# In production the URL is PostgreSQL, e.g. "postgresql+asyncpg://app@db/shop".
from contextlib import asynccontextmanager

import strawberry
from fastapi import FastAPI
from fastapi.testclient import TestClient
from sqlalchemy import select
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from strawberry.fastapi import GraphQLRouter

engine = create_async_engine("sqlite+aiosqlite:///:memory:")
Session = async_sessionmaker(engine, expire_on_commit=False)

class Base(DeclarativeBase):
    pass

class OrderRow(Base):  # SQLAlchemy: a table
    __tablename__ = "orders"
    id: Mapped[int] = mapped_column(primary_key=True)
    customer: Mapped[str]
    total_cents: Mapped[int]

@strawberry.type
class Order:  # Strawberry: a GraphQL type
    id: int
    total_cents: int

@strawberry.type
class Query:
    @strawberry.field
    async def orders(self, info: strawberry.Info, customer: str) -> list[Order]:
        session = info.context["session"]
        rows = await session.scalars(select(OrderRow).where(OrderRow.customer == customer))
        return [Order(id=r.id, total_cents=r.total_cents) for r in rows]

async def get_context():  # a FastAPI dependency: one session per request
    async with Session() as session:
        yield {"session": session}

@asynccontextmanager
async def lifespan(app: FastAPI):  # create and fill the table at startup
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
    async with Session() as s:
        s.add_all([OrderRow(customer="ada", total_cents=1299),
                   OrderRow(customer="bob", total_cents=500)])
        await s.commit()
    yield

app = FastAPI(lifespan=lifespan)
app.include_router(GraphQLRouter(strawberry.Schema(Query), context_getter=get_context),
                   prefix="/graphql")

@app.get("/health")  # ordinary REST routes sit beside it
def health():
    return {"ok": True}

with TestClient(app) as client:
    query = '{ orders(customer: "ada") { id totalCents } }'
    print(client.post("/graphql", json={"query": query}).json())
    # {'data': {'orders': [{'id': 1, 'totalCents': 1299}]}}
    bad = client.post("/graphql", json={"query": "{ orders { id } }"})
    print(bad.status_code, bad.json()["errors"][0]["message"])
    # 200 Argument 'Query.orders(customer:)' of type 'String!' is required, but it was not provided.
    print(client.get("/health").json())  # {'ok': True}

See it in a real stack

GraphQL API in Python →
Live model
What you’re watchingClients post GraphQL queries (blue) to one endpoint. Uvicorn hands each request to FastAPI over ASGI, FastAPI routes it to Strawberry’s router, and graphql-core validates and executes it; resolvers load rows through SQLAlchemy from PostgreSQL, and the answer travels back the same way. Pink queries fail validation, so graphql-core answers them with an error list and the database never sees them. Green requests are plain REST calls that FastAPI validates with Pydantic models.
Where it came from2018–2026

Sebastián Ramírez had spent several years building APIs for machine learning and distributed systems and leading the teams behind them. Ramírez’s favourite stack was Flask with Flask-apispec, Marshmallow and Webargs, and Ramírez built full-stack project generators on it. But the schemas were Marshmallow classes rather than Python types, and with plug-ins such as APISpec the API description was YAML in docstrings, which went stale whenever someone changed the code and forgot the YAML. The closest thing to what Ramírez wanted was APIStar, which declared parameters with type hints and generated OpenAPI, until its creator turned to Starlette and APIStar stopped being a web framework.

Before writing FastAPI, Ramírez spent several months studying the OpenAPI, JSON Schema and OAuth2 specifications, and tried out API designs in PyCharm, VS Code and Jedi-based editors. Ramírez picked Pydantic for data and Starlette for the web layer and contributed to both, including making Pydantic fully compliant with JSON Schema. FastAPI 0.1.0 was published on December 8, 2018, and its docs call it a “spiritual successor” to APIStar.

Because FastAPI hands all data work to Pydantic, Pydantic’s rewrite shaped it. FastAPI 0.100.0 (July 2023) supported Pydantic 2, whose core is written in Rust, while keeping Pydantic 1 working, and 0.126.0 (December 2025) dropped Pydantic 1. In 2026 it moved to Python 3.10 or later, added support for Starlette 1.0 in 0.133.0, and gained native OpenTelemetry support in 0.142.0, still under a 0.x version number.

Why it’s built this way

Build on Starlette and Pydantic instead of from scratch
Starlette already had the routing, WebSockets and speed, but no validation, serialization or docs; Pydantic did validation and JSON Schema from type hints. FastAPI only adds the layer that joins them, and the FastAPI class subclasses Starlette.
Standard Python type hints, not a custom schema language
APISpec’s YAML docstrings were a second language inside Python strings that editors couldn’t help with. Plain type hints give completion and type checks, and the design was tested in the editors that, by the Python Developer Survey, about 80% of Python developers use.
Open standards for the API description
Hug generated its own JSON description of an API, not OpenAPI, so tools such as Swagger UI couldn’t easily use it. FastAPI emits OpenAPI and JSON Schema, so standard docs UIs and client generators work with it.
Dependencies declared where they’re used
The dependency injection in NestJS, APIStar and Molten needed components registered up front. In FastAPI a dependency is a function named in a parameter’s Depends(), so there is nothing to register.

Timeline

  1. 2018

    0.1.0 published on PyPI on December 8

  2. 2019

    0.6.1 documents GraphQL integration in February

  3. 2023

    0.100.0 supports Pydantic 2 on July 7

  4. 2025

    0.126.0 drops Pydantic 1 on December 20

  5. 2026

    0.129.0 requires Python 3.10 (February); 0.133.0 supports Starlette 1.0

  6. 2026

    0.142.0 adds native OpenTelemetry support on September 29

Sources16 · facts checked 9 October 2026

Improve this page on GitHub