定义类型、查询与变更
使用 Strawberry 构建类型化 GraphQL 模式,并将其挂载到具有共享依赖的 FastAPI 应用上。
定义类型、查询与变更 是 CoddyKit 上的免费 FastAPI Backend Development Bootcamp 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 FastAPI Backend Development Bootcamp 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 FastAPI Backend Development Bootcamp 课程共包含 4 节课。
本课时的部分内容尚未翻译,以英文显示。
Why Strawberry for GraphQL on FastAPI
Strawberry is a code-first GraphQL library for Python that uses dataclasses and type hints to define your schema. Instead of writing GraphQL SDL by hand, you write plain Python classes and Strawberry derives the schema from them.
- Code-first: the Python types ARE the source of truth — the SDL is generated.
- Type-safe: standard type hints (
int,str,list[str],Optional) map directly to GraphQL types. - ASGI-native: ships a router that mounts cleanly on a FastAPI app, sharing its event loop and dependency system.
In this lesson we build a typed schema (types, a Query, and a Mutation) and mount it on FastAPI with shared dependencies.
Defining an Object Type
A GraphQL object type is just a class decorated with @strawberry.type. Each annotated attribute becomes a field. Type hints determine the GraphQL field type: int becomes Int, str becomes String, and a non-optional field becomes non-null (!).
- Use
strawberry.IDfor identifier fields — it serializes as a string but signals identity semantics. Optional[...](orX | None) makes a field nullable.
import strawberry
from typing import Optional
@strawberry.type
class Book:
id: strawberry.ID
title: str
author: str
pages: int
summary: Optional[str] = NoneThe Query Root Type
Every GraphQL schema needs a Query root — the entry point for reads. You declare it as a @strawberry.type whose fields are resolved by methods decorated with @strawberry.field.
- The method's return annotation defines the field's GraphQL type.
- Method parameters (other than
self) become GraphQL arguments. - Returning a
list[Book]produces a non-null list of non-null books:[Book!]!.
import strawberry
@strawberry.type
class Query:
@strawberry.field
def books(self) -> list[Book]:
return [
Book(id="1", title="Dune", author="Herbert", pages=412),
Book(id="2", title="1984", author="Orwell", pages=328),
]
@strawberry.field
def book(self, id: strawberry.ID) -> Book | None:
for b in self.books():
if b.id == id:
return b
return NoneBuilding the Schema
strawberry.Schema ties root types together. At minimum you pass query=Query; later you add mutation=Mutation. Building the schema validates your types and lets you print the generated SDL — a great sanity check.
Here is a fully standalone example: define a type, a query, build the schema, and execute a query synchronously with schema.execute_sync. No server or framework needed.
import strawberry
@strawberry.type
class Book:
id: strawberry.ID
title: str
author: str
@strawberry.type
class Query:
@strawberry.field
def books(self) -> list[Book]:
return [Book(id="1", title="Dune", author="Herbert")]
schema = strawberry.Schema(query=Query)
result = schema.execute_sync("{ books { id title author } }")
print(result.errors)
print(result.data)Field Arguments and Defaults
GraphQL arguments come straight from resolver parameters. A parameter with a default value becomes an optional argument; without a default it is required.
- Use
typing.Optional+ a default to express a nullable, optional argument. - Strawberry coerces incoming argument values to the annotated Python type automatically.
Below, limit defaults to 10 and genre is an optional filter.
import strawberry
from typing import Optional
@strawberry.type
class Book:
id: strawberry.ID
title: str
genre: str
LIBRARY = [
Book(id="1", title="Dune", genre="scifi"),
Book(id="2", title="It", genre="horror"),
]
@strawberry.type
class Query:
@strawberry.field
def books(self, limit: int = 10, genre: Optional[str] = None) -> list[Book]:
items = LIBRARY if genre is None else [b for b in LIBRARY if b.genre == genre]
return items[:limit]
schema = strawberry.Schema(query=Query)
print(schema.execute_sync('{ books(genre: "scifi") { title } }').data)Input Types for Mutations
Mutations that accept structured data should use an input type: a class decorated with @strawberry.input. Input types are the GraphQL equivalent of a request body — they keep mutation signatures clean and self-documenting.
- Fields without defaults are required input fields.
- Reuse the same input across create/update flows by making fields optional where appropriate.
import strawberry
from typing import Optional
@strawberry.input
class AddBookInput:
title: str
author: str
pages: Optional[int] = NoneThe Mutation Root Type
The Mutation root mirrors Query but expresses writes. Each method is a @strawberry.mutation. Convention: take an input type, perform the side effect, and return the created or updated object so the client can read fresh fields in one round trip.
This example keeps an in-memory store and returns the new Book. It is fully standalone and runnable.
import strawberry
_DB: list["Book"] = []
@strawberry.type
class Book:
id: strawberry.ID
title: str
author: str
@strawberry.input
class AddBookInput:
title: str
author: str
@strawberry.type
class Query:
@strawberry.field
def books(self) -> list[Book]:
return _DB
@strawberry.type
class Mutation:
@strawberry.mutation
def add_book(self, data: AddBookInput) -> Book:
book = Book(id=str(len(_DB) + 1), title=data.title, author=data.author)
_DB.append(book)
return book
schema = strawberry.Schema(query=Query, mutation=Mutation)
q = 'mutation { addBook(data: {title: "Dune", author: "Herbert"}) { id title } }'
print(schema.execute_sync(q).data)Async Resolvers
Because Strawberry runs on ASGI, resolvers can be async. This matters on FastAPI: your resolvers will await database calls, HTTP clients, or cache lookups without blocking the event loop.
- Just declare
async def— Strawberry awaits it for you. - Mix sync and async resolvers freely in the same schema.
- Use async resolvers for any I/O so a single GraphQL request with many fields stays non-blocking.
import strawberry
import asyncio
@strawberry.type
class Stats:
total_books: int
async def fetch_count() -> int:
await asyncio.sleep(0) # stand-in for an async DB call
return 42
@strawberry.type
class Query:
@strawberry.field
async def stats(self) -> Stats:
return Stats(total_books=await fetch_count())
schema = strawberry.Schema(query=Query)
print(asyncio.run(schema.execute("{ stats { totalBooks } }")).data)Mounting on FastAPI with GraphQLRouter
Strawberry ships strawberry.fastapi.GraphQLRouter, an APIRouter you mount with app.include_router. It serves the GraphQL endpoint and an in-browser IDE (GraphiQL) at the same path.
- Pass your built
schemato the router. - Mount it under a path like
/graphql. - The router uses FastAPI's event loop, so async resolvers and FastAPI startup/shutdown events work together.
This is framework code, so it is not runnable on a bare judge.
import strawberry
from fastapi import FastAPI
from strawberry.fastapi import GraphQLRouter
@strawberry.type
class Query:
@strawberry.field
def hello(self) -> str:
return "world"
schema = strawberry.Schema(query=Query)
graphql_app = GraphQLRouter(schema)
app = FastAPI()
app.include_router(graphql_app, prefix="/graphql")Sharing Dependencies via Context
The big payoff of mounting on FastAPI is shared dependencies. Pass a context_getter to GraphQLRouter — it is a FastAPI dependency callable, so it can itself Depends on a DB session, the current user, or a settings object.
Whatever the context getter returns is exposed to resolvers via strawberry.Info at info.context. This is how authentication and database sessions flow from FastAPI into your GraphQL resolvers.
from fastapi import Depends
from strawberry.fastapi import GraphQLRouter
async def get_db():
# yield a real async session in production
yield {"connection": "db-session"}
async def get_context(db=Depends(get_db)):
return {"db": db, "role": "reader"}
graphql_app = GraphQLRouter(schema, context_getter=get_context)Reading Context Inside Resolvers
To use the shared context, add an info: strawberry.Info parameter to a resolver. Strawberry injects it automatically and it never appears as a GraphQL argument. Access your dependencies through info.context.
info.context["db"]— the session provided bycontext_getter.- Use it to authorize: read the current user and raise on missing permissions.
- The same context object is shared across every resolver in a single request.
import strawberry
@strawberry.type
class Query:
@strawberry.field
def current_role(self, info: strawberry.Info) -> str:
return info.context["role"]
@strawberry.field
def secret(self, info: strawberry.Info) -> str:
if info.context["role"] != "admin":
raise Exception("forbidden")
return "top-secret"Quick Check: Sharing the DB session
You mounted a Strawberry schema on FastAPI and need each GraphQL resolver to use the same per-request database session that your REST endpoints get from a FastAPI dependency. What is the idiomatic Strawberry + FastAPI way to wire this up?
Recap
You built a typed GraphQL schema with Strawberry and mounted it on FastAPI:
- Types:
@strawberry.typeclasses with type-hinted fields;strawberry.IDfor identifiers andOptionalfor nullable fields. - Query: the read root, with
@strawberry.fieldresolvers whose parameters become GraphQL arguments. - Mutations:
@strawberry.mutationmethods that take an@strawberry.inputtype and return the affected object. - Schema:
strawberry.Schema(query=Query, mutation=Mutation), verifiable withexecute_sync. - FastAPI integration: mount with
GraphQLRouter, and share DB sessions and auth throughcontext_getter+info.context, reusing FastAPI's dependency injection.
This code-first, type-safe approach keeps your GraphQL API and your FastAPI app speaking the same language — Python type hints.
常见问题解答
「定义类型、查询与变更」课时是免费的吗?
是的 — 「定义类型、查询与变更」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 FastAPI Backend Development Bootcamp 课程的其余内容,请升级到 CoddyKit PRO。 FastAPI Backend Development Bootcamp 课程共包含 4 节课。
「定义类型、查询与变更」这节课中我会学到什么?
使用 Strawberry 构建类型化 GraphQL 模式,并将其挂载到具有共享依赖的 FastAPI 应用上。 你通过在浏览器中直接运行的动手代码来练习 FastAPI Backend Development Bootcamp,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 FastAPI Backend Development Bootcamp 需要有经验吗?
无需任何先前经验。CoddyKit 上的 FastAPI Backend Development Bootcamp 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。
「定义类型、查询与变更」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 FastAPI Backend Development Bootcamp 课中编写并运行代码吗?
能。每节 FastAPI Backend Development Bootcamp 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。