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)
- Part of
- Built on Starlette
- 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
defendpoints and dependencies share Starlette’s thread pool, 40 threads by default; blocking I/O insideasync defholds 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
What FastAPI uses
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) # 422GraphQL 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.
# 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 →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
FastAPIclass subclassesStarlette. - 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
- 2018
0.1.0 published on PyPI on December 8
- 2019
0.6.1 documents GraphQL integration in February
- 2023
0.100.0 supports Pydantic 2 on July 7
- 2025
0.126.0 drops Pydantic 1 on December 20
- 2026
0.129.0 requires Python 3.10 (February); 0.133.0 supports Starlette 1.0
- 2026
0.142.0 adds native OpenTelemetry support on September 29
Sources16 · facts checked 9 October 2026
- FastAPI docs: Features
- FastAPI README (installation, example, /docs and /redoc)
- FastAPI docs: Path Parameters (data validation errors)
- FastAPI docs: Request Body
- FastAPI docs: Response Model (validating and filtering output)
- FastAPI docs: Additional Responses in OpenAPI (the default 422 Validation Error response)
- FastAPI docs: Dependencies
- FastAPI docs: Concurrency and async / await (def runs in a thread pool)
- Starlette docs: Thread Pool (40 tokens by default, shared with FastAPI)
- FastAPI docs: About FastAPI versions
- FastAPI docs: GraphQL
- Strawberry docs: FastAPI integration (GraphQLRouter, context_getter)
- FastAPI docs: SQL (Relational) Databases (SQLModel)
- FastAPI docs: Benchmarks
- FastAPI release notes (0.143.0 on 2026-10-08; 0.142.0 native OpenTelemetry)
- FastAPI LICENSE (MIT)