Strawberry with FastAPI
|
This section documents the current GraphQL specification (October 2021), plus the working draft for
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, as GraphQL tooling iterates quickly. |
Strawberry is a code-first, dataclass-driven GraphQL library for Python, and strawberry.fastapi.GraphQLRouter
is its official integration for mounting a schema onto a FastAPI application. This page covers defining types
and resolvers, mounting the router, threading FastAPI’s own dependency injection into resolver context, the
built-in GraphQL IDEs, and subscriptions over WebSocket and Server-Sent Events.
Prerequisites
This page assumes strawberry-graphql[fastapi] is already installed and that a uvicorn development server is
available — both covered in Getting started with Python
GraphQL. Every example below assumes the following imports are in scope:
import asyncio
import strawberry
from typing import AsyncGenerator
from fastapi import Depends, FastAPI, Request
from strawberry.fastapi import GraphQLRouter
Defining types with @strawberry.type and @strawberry.field
A Strawberry type is a plain Python dataclass decorated with @strawberry.type; each attribute becomes a
GraphQL field, with the Python type hint mapped onto the matching GraphQL scalar or object type. A method
decorated with @strawberry.field becomes a computed field, resolved on demand rather than stored as data:
@strawberry.type
class Author:
id: strawberry.ID
name: str
@strawberry.type
class Book:
id: strawberry.ID
title: str
author: Author
@strawberry.field
def display_title(self) -> str:
return f"{self.title} by {self.author.name}"
display_title above is a sync resolver — a plain method body, no async/await involved — appropriate
for a field computed purely from data already in memory. The next section covers when to reach for async def
instead. Scalars, enums, interfaces, unions, generics, and input types are covered in depth in
Strawberry — schema and features; this page
only introduces enough of the type system to build a runnable FastAPI integration.
The Query, Mutation, and Subscription root types
Every Strawberry schema is assembled from up to three root types — Query is mandatory, Mutation and
Subscription are added only if the API needs them — each a @strawberry.type whose fields are the schema’s
top-level operations:
@strawberry.type
class Query:
@strawberry.field
def book(self, id: strawberry.ID) -> Book:
return repository.get_book(id)
@strawberry.type
class Mutation:
@strawberry.mutation
def add_book(self, title: str, author_id: strawberry.ID) -> Book:
return repository.create_book(title, author_id)
schema = strawberry.Schema(query=Query, mutation=Mutation)
strawberry.Schema(…) builds the executable schema from these roots; subscription= joins query= and
mutation= once a Subscription root type exists (covered later on this page). The Query/Mutation split
mirrors the language-agnostic model in
Queries \& fields and
Mutations — Strawberry does not change GraphQL’s read/write semantics,
only how the roots are declared in Python.
Sync and async resolvers
A @strawberry.field-decorated method can be either a plain def or an async def; Strawberry’s execution
engine awaits whichever resolvers declare themselves async and calls the rest synchronously, so the two styles
mix freely across a single schema. Reach for async def whenever a resolver performs I/O — a database query, an
HTTP call to another service, a DataLoader batch load — so that the ASGI event loop can service other
requests (or other fields of the same query, executed concurrently) while the resolver waits:
@strawberry.type
class Query:
@strawberry.field
def book(self, id: strawberry.ID) -> Book: # sync: in-memory lookup
return repository.get_book(id)
@strawberry.field
async def recommended_books(self, info: strawberry.Info) -> list[Book]:
async with info.context["db_session"] as session: # async: real I/O
return await session.fetch_recommended_books()
Mixing a slow synchronous resolver into an otherwise async request path blocks the event loop for every other
concurrent request, so a resolver doing blocking I/O (a synchronous database driver, a blocking HTTP client)
should either be rewritten against an async driver or offloaded with asyncio.to_thread rather than left as a
plain def. Execution \& resolvers covers how the
execution engine schedules and awaits resolvers language-agnostically; this section only covers the Python
sync/async split on top of it.
Mounting a GraphQLRouter on a FastAPI app
strawberry.fastapi.GraphQLRouter wraps a strawberry.Schema as a FastAPI APIRouter, handling the POST
/graphql query/mutation endpoint, the GraphiQL IDE on GET, and (as covered further down) the WebSocket
upgrade for subscriptions, all from one router mounted at a single prefix:
app = FastAPI()
graphql_app = GraphQLRouter(schema)
app.include_router(graphql_app, prefix="/graphql")
Because GraphQLRouter is a regular APIRouter, it composes with the rest of a FastAPI application exactly
like any other router — other REST routes, middleware, and startup/shutdown events on the same app are
unaffected, and nothing about mounting it requires the rest of the application to be GraphQL-aware. See
Strawberry — FastAPI for the full integration reference,
including the ASGI-level (non-FastAPI) mounting option for applications that only need the router’s underlying
ASGI app.
The context_getter: injecting FastAPI dependencies
Every resolver receives a strawberry.Info argument whose .context attribute is a place for the transport
layer to hand resolvers whatever request-scoped state they need — the incoming request, a database session, the
authenticated user. GraphQLRouter’s `context_getter parameter is itself a FastAPI dependency, which means it
can declare Depends(…) parameters of its own and FastAPI resolves them exactly as it would for a normal
route handler, before the GraphQL request executes:
async def get_db_session() -> AsyncGenerator[Session, None]:
async with SessionLocal() as session:
yield session
async def get_current_user(request: Request) -> User | None:
return await authenticate(request)
async def get_context(
db_session: Session = Depends(get_db_session),
current_user: User | None = Depends(get_current_user),
) -> dict:
return {"db_session": db_session, "current_user": current_user}
graphql_app = GraphQLRouter(schema, context_getter=get_context)
A resolver then reads info.context["db_session"] or info.context["current_user"] exactly like the
recommended_books resolver did earlier. The dictionary shape above is the simplest option; a context_getter
can instead return an instance of a class inheriting strawberry.fastapi.BaseContext, giving resolvers
attribute access (info.context.current_user) instead of dictionary keys, and a natural place to add helper
methods alongside the request-scoped data. Either way, the mechanism is the same: FastAPI’s own dependency
injection graph runs once per request, and its results land in info.context for every resolver in that
request to share.
GraphiQL and other GraphQL IDEs
GraphQLRouter serves an in-browser GraphQL IDE on GET requests to its mounted path by default, controlled by
the graphql_ide parameter:
graphql_app = GraphQLRouter(
schema,
graphql_ide="apollo-sandbox", # "graphiql" (default), "apollo-sandbox", "pathfinder", or None
)
Passing None disables the IDE entirely, which most teams do once an environment moves from local development
toward production — the IDE offers autocomplete and schema exploration against introspection, which is
convenience during development rather than something a production deployment needs to expose. The three
language-agnostic IDEs themselves (GraphiQL, Apollo Sandbox, and their historical predecessor GraphQL
Playground) are introduced in Getting started with GraphQL.
Subscriptions over WebSocket and Server-Sent Events
A Subscription root type looks like Query/Mutation except each field is decorated @strawberry.subscription
and returns an AsyncGenerator — the resolver body `yield`s a value each time the subscription should push an
update to the client, rather than returning a single result:
@strawberry.type
class Subscription:
@strawberry.subscription
async def book_added(self) -> AsyncGenerator[Book, None]:
async for book in book_added_events():
yield book
schema = strawberry.Schema(query=Query, mutation=Mutation, subscription=Subscription)
GraphQLRouter serves this subscription over WebSocket by default, supporting both the legacy graphql-ws
protocol id (implemented by the older subscriptions-transport-ws package, kept for backward compatibility
with older clients) and the newer graphql-transport-ws protocol id (implemented by the actively maintained
graphql-ws package) that current clients should prefer; both are negotiated automatically from the client’s
requested Sec-WebSocket-Protocol header, with no extra configuration needed for the common case. Server-Sent Events is
an HTTP-only alternative transport for subscriptions — useful where a WebSocket upgrade is impractical (some
proxies, some serverless deployments) — enabled by opting a schema into the SSE protocol explicitly rather than
being on by default alongside the WebSocket protocols.
Authentication for a subscription connection is handled differently from a query or mutation, because a
WebSocket’s initial connection parameters (not an HTTP header set once per request) are where a client
typically passes a token; GraphQLRouter exposes an on_ws_connect hook a server can override to inspect those
parameters and raise a ConnectionRejectionError to refuse the connection before any subscription resolver
runs. Subscriptions covers the subscription operation type and its
WebSocket sub-protocols language-agnostically; see
Strawberry — Subscriptions for the full Python reference,
including cancellation handling and the ASGI/AIOHTTP/Django integrations beyond FastAPI.
The FastAPI + Strawberry request path
Tying the pieces on this page together, a single GraphQL request over HTTP moves through GraphQLRouter, its
context_getter, the schema’s execution engine, and the resolvers defined above, with DataLoader batching
(covered in depth on Performance \& N+1) sitting between a
resolver and the datastore it ultimately reads from:
This diagram is specific to the FastAPI + Strawberry integration on this page; the language-agnostic parse/validate/execute/respond lifecycle it sits inside of is covered once, as the canonical reference, in Getting started with GraphQL.
Where to go from here
This page covered enough of Strawberry’s type system and FastAPI integration to run a working GraphQL server; three siblings go deeper on adjacent concerns:
-
Strawberry — schema and features covers scalars, enums, interfaces, unions, generics, input types, permission classes,
DataLoaderin depth, Relay pagination helpers, and schema export/codegen. -
Ariadne — schema-first covers the alternative, SDL-first way to build a Python GraphQL server, for teams that prefer defining the schema as text rather than as decorated Python classes.
-
Serving over HTTP covers the wire-level request/response contract that
GraphQLRouterimplements underneath the Python API shown on this page.
|
This page was generated with the assistance of AI. Verify the exact |