Cloud Parts

API frameworks

Libraries that turn ordinary functions into HTTP or GraphQL endpoints: they route each request, parse and check its input, serialize the result and describe the API for docs and generated clients.

2 technologies on this page ↓

How it works

A web server and an API framework split the work. The server, such as Uvicorn, accepts connections and parses HTTP, then calls your application through a standard interface. In Python that interface is WSGI, a single synchronous callable that takes a request and returns a response, or its successor ASGI, a single async callable that gets a scope describing the connection plus receive and send functions for messages, which also suits WebSockets and other long-lived connections. The framework is the application on the other side of that call. Its router maps a method and path, such as POST /orders, to one of your functions. Before calling it, the framework binds request data to the function’s parameters: path segments, query strings, headers and the JSON body, converted to the declared types and validated. A body with quantity: 0 where the model says the value must be greater than 0 is rejected with an error (a 422 in FastAPI), and your function never runs. On the way out the framework serializes the return value to JSON, filtering it through a response model so internal fields stay inside. Because the same declarations describe every endpoint, frameworks such as FastAPI generate an OpenAPI document from them, which Swagger UI, ReDoc and client generators read. A GraphQL library works differently. There is one endpoint, and the work happens in an engine: it parses the query text into a syntax tree, validates it against the schema (an unknown field or a missing required argument fails here, before any data is touched), then executes it by calling a resolver function for each requested field and assembling a result shaped like the query. Schema libraries on top of the engine differ in where the schema comes from: SDL text with resolvers attached to it (schema-first), or classes and type hints in your code (code-first).

Think of it as

A hotel front desk. The desk doesn’t clean rooms or cook; it sends each guest to the right department (routing), checks that their booking form is complete before passing it on (validation), and hands back every bill in one standard layout (serialization). The brochure listing every service is printed from the same forms the desk checks, so it’s never out of date (generated docs).

Reach for it when

  • Any HTTP or GraphQL service with more than a handful of endpoints, where hand-written parsing and checking would repeat in every handler
  • Public or partner APIs that need an accurate OpenAPI document or GraphQL schema for docs and generated clients
  • Input from untrusted callers: declare the fields and types once, and malformed requests are turned away before your code runs
  • Adding GraphQL to an existing web app: most GraphQL libraries mount as one route or sub-application beside the other endpoints

Think twice when

  • A single webhook or health check: a bare ASGI or WSGI callable, or a small toolkit such as Starlette, is enough
  • Hot paths where per-request validation and serialization cost too much; FastAPI’s own docs point out it can’t be faster than the Starlette layer it builds on
  • Choosing by benchmark numbers alone: most benchmarks don’t exercise the validation and serialization a framework adds, let alone your database work

A real example

A shop’s mobile app sends POST /orders with {"sku": "A-1", "quantity": "3"}. Uvicorn parses the HTTP request and calls the FastAPI app over ASGI. FastAPI’s router picks create_order, Pydantic turns the string "3" into the integer 3, and the function returns a row that also holds an internal cost_cents field. The response model drops that field, so the client gets 201 and {"id": 42, "sku": "A-1", "quantity": 3}. The same request with quantity: 0 gets 422 and the message “Input should be greater than 0”, and create_order never runs. The OpenAPI document behind the /docs page is built from the same two models.

Kinds of api frameworks

Specific products, and what each does differently.

GraphQL libraries →

An engine that parses, validates and executes GraphQL queries, plus a schema library on top that defines types and resolvers either as SDL text or as code.

Who does what in a Python API stack#

Each layer only talks to its neighbours, so most of them can be swapped: another ASGI server under the same app, or another GraphQL library on the same engine.

ExampleIts job
ASGI serverUvicornAccepts connections, parses HTTP and calls the app as app(scope, receive, send); --workers runs several processes
Web toolkitStarletteRouting, request and response objects, middleware, WebSockets, background tasks and a test client
API frameworkFastAPIBinds parameters from type hints, runs dependencies and generates OpenAPI; its FastAPI class subclasses Starlette
Data modelsPydanticValidates and converts input, serializes output and emits JSON Schema
GraphQL enginegraphql-coreParses, validates and executes queries; a Python port of the GraphQL.js reference implementation
GraphQL schema libraryStrawberry and Graphene (code-first), Ariadne (schema-first)Build the graphql-core schema and plug into the web framework as a router or a mounted ASGI app

What a GraphQL engine does, step by step#

graphql-core on its own, with a schema written in SDL and one resolver. graphql_sync runs all three steps; parse and validate are shown separately to make them visible.

python
# pip install graphql-core
from graphql import build_schema, graphql_sync, parse, validate

schema = build_schema("""
  type Order { id: ID!  totalCents: Int! }
  type Query { orders(customer: String!): [Order!]! }
""")  # schema-first: the SDL text is the contract

ORDERS = {"ada": [{"id": "1", "totalCents": 1299}]}
root = {"orders": lambda info, customer: ORDERS.get(customer, [])}  # the resolver

document = parse('{ orders(customer: "ada") { totalCents } }')  # 1. text to an AST
print(validate(schema, document))  # 2. check it against the schema: []
print(graphql_sync(schema, '{ orders(customer: "ada") { totalCents } }', root_value=root))
# 3. execute: ExecutionResult(data={'orders': [{'totalCents': 1299}]}, errors=None)
print(graphql_sync(schema, '{ orders(customer: "ada") { cost } }').errors[0].message)
# Cannot query field 'cost' on type 'Order'.  (rejected before any resolver runs)

See it in a real stack

Architectures that use api frameworks

In code · python

# pip install fastapi httpx2
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field

class OrderIn(BaseModel):    # what a caller may send
    sku: str
    quantity: int = Field(gt=0)

class OrderOut(BaseModel):   # what the API promises to send back
    id: int
    sku: str
    quantity: int

app = FastAPI()

@app.post("/orders", response_model=OrderOut, status_code=201)
def create_order(order: OrderIn):  # route + parsed, validated body
    row = {"id": 42, **order.model_dump(), "cost_cents": 870}
    return row  # filtered through OrderOut on the way out

client = TestClient(app)  # calls the ASGI app in-process, no server needed
r = client.post("/orders", json={"sku": "A-1", "quantity": "3"})
print(r.status_code, r.json())  # 201 {'id': 42, 'sku': 'A-1', 'quantity': 3}
r = client.post("/orders", json={"sku": "A-1", "quantity": 0})
print(r.status_code, r.json()["detail"][0]["msg"])  # 422 Input should be greater than 0
print(list(app.openapi()["paths"]))  # ['/orders']: docs come from the same types

Often confused with

Where it came from2003–2026

In the early 2000s each Python web framework (Zope, Quixote, Webware, Twisted Web and others) ran only on certain servers, so choosing a framework limited the choice of server and the other way round. PEP 333, created in December 2003, defined WSGI: one standard interface between web servers and Python applications, after the portability Java’s servlet API gave Java frameworks. Servers and frameworks could then evolve separately. WSGI’s single synchronous callable can’t keep a WebSocket open, so ASGI kept the idea of one callable but made it asynchronous and message-based; its 3.0 specification is dated March 2019.

Frameworks above that interface first handled routing and HTML. Building APIs added three more chores: parsing and validating input, serializing output and documenting endpoints. In Python these came as separate plug-ins, such as Marshmallow for schemas, Webargs for parsing and APISpec for OpenAPI written as YAML inside docstrings, and the docs went stale whenever someone changed the code but not the YAML. Swagger gave API descriptions a standard format, and in November 2015 SmartBear, Google, Microsoft and others formed the OpenAPI Initiative under the Linux Foundation to carry it on as OpenAPI. Hug and then APIStar read parameter types from Python type hints. When APIStar’s creator turned to Starlette in 2018, Sebastián Ramírez built FastAPI on Starlette and Pydantic, so one set of type hints drives validation, serialization and the OpenAPI document.

GraphQL took another path. Facebook started it in 2012 while rebuilding its native mobile apps, because neither REST resources nor its SQL-like FQL tables fitted the data News Feed needed, and in 2015 it published a specification and a reference implementation, GraphQL.js. Other languages ported that engine. In Python, graphql-core 3 follows GraphQL.js closely, and Strawberry, Ariadne and Graphene build their different schema styles on top of it.

Why it’s built this way

A standard callable between server and framework
PEP 333 argued that Python’s many frameworks each tied users to particular servers, while Java’s servlet API let any framework run on any server. One agreed interface, WSGI and later ASGI, lets you swap Uvicorn for another server without touching the app.
Declare inputs as types instead of parsing them by hand
Without a validation layer every handler repeats the same checks. Reading types from function signatures and models means one declaration gives parsing, conversion, validation and editor completion.
Generate the API description from the code that validates
APISpec’s YAML in docstrings drifted from the real parameters and schemas. Building OpenAPI from the same declarations the framework enforces keeps the docs and the behaviour in step.
Keep the GraphQL engine separate from the schema style
Parsing, validation and execution are fixed by the specification, so one engine can serve several libraries. graphql-core does that work for Strawberry, Ariadne and Graphene, which compete only on how you define the schema.

Timeline

  1. 2003

    PEP 333 defines WSGI, a standard interface between Python web servers and applications

  2. 2012

    Facebook starts GraphQL while rebuilding its native mobile apps

  3. 2015

    Facebook publishes the GraphQL specification and GraphQL.js; the OpenAPI Initiative forms in November

  4. 2018

    Starlette appears in June and FastAPI 0.1.0 in December, both built on ASGI

  5. 2019

    ASGI 3.0 specification, dated March 20

  6. 2023

    Pydantic 2, with a Rust core, ships in June; FastAPI 0.100.0 supports it in July

  7. 2026

    Starlette 1.0 in March, after nearly eight years of 0.x releases

Sources16

Improve this page on GitHub