REST is great, but sometimes clients need more flexibility. GraphQL lets clients request exactly the data they need. Here's how to build a GraphQL API in Python.
Why GraphQL?
- No over-fetching: client gets only requested fields
- No under-fetching: get related data in one request
- Strongly typed schema serves as documentation
- Great for mobile apps and complex frontends
Setup with Strawberry
pip install strawberry-graphql[fastapi]
Basic Schema
import strawberry
from strawberry.fastapi import GraphQLRouter
from fastapi import FastAPI
@strawberry.type
class User:
id: int
name: str
email: str
@strawberry.type
class Post:
id: int
title: str
content: str
author: User
# Sample data
users_db = [
{"id": 1, "name": "Alice", "email": "alice@example.com"},
{"id": 2, "name": "Bob", "email": "bob@example.com"},
]
posts_db = [
{"id": 1, "title": "Hello GraphQL", "content": "GraphQL is awesome", "author_id": 1},
{"id": 2, "title": "Python Tips", "content": "Use type hints", "author_id": 1},
{"id": 3, "title": "Docker Guide", "content": "Containers 101", "author_id": 2},
]
@strawberry.type
class Query:
@strawberry.field
def users(self) -> list[User]:
return [User(**u) for u in users_db]
@strawberry.field
def user(self, id: int) -> User | None:
for u in users_db:
if u["id"] == id:
return User(**u)
return None
@strawberry.field
def posts(self, limit: int = 10) -> list[Post]:
results = []
for p in posts_db[:limit]:
author = next(u for u in users_db if u["id"] == p["author_id"])
results.append(Post(id=p["id"], title=p["title"], content=p["content"], author=User(**author)))
return results
schema = strawberry.Schema(query=Query)
app = FastAPI()
app.include_router(GraphQLRouter(schema), prefix="/graphql")
Querying
# Get only what you need
query {
users {
name
email
}
}
# Nested data in one request
query {
posts(limit: 5) {
title
author {
name
}
}
}
# Single user
query {
user(id: 1) {
name
email
}
}
Mutations
@strawberry.input
class CreatePostInput:
title: str
content: str
author_id: int
@strawberry.type
class Mutation:
@strawberry.mutation
def create_post(self, input: CreatePostInput) -> Post:
new_id = max(p["id"] for p in posts_db) + 1
author = next(u for u in users_db if u["id"] == input.author_id)
post_data = {
"id": new_id,
"title": input.title,
"content": input.content,
"author_id": input.author_id,
}
posts_db.append(post_data)
return Post(id=new_id, title=input.title, content=input.content, author=User(**author))
@strawberry.mutation
def delete_post(self, id: int) -> bool:
for i, p in enumerate(posts_db):
if p["id"] == id:
posts_db.pop(i)
return True
return False
schema = strawberry.Schema(query=Query, mutation=Mutation)
mutation {
createPost(input: { title: "New Post", content: "Hello!", authorId: 1 }) {
id
title
author { name }
}
}
DataLoader: Solving N+1 Queries
from strawberry.dataloader import DataLoader
async def load_users(ids: list[int]) -> list[User]:
# Single batch query instead of N queries
users = await db.fetch_all("SELECT * FROM users WHERE id = ANY($1)", ids)
user_map = {u["id"]: User(**u) for u in users}
return [user_map.get(id) for id in ids]
user_loader = DataLoader(load_fn=load_users)
@strawberry.type
class Post:
id: int
title: str
author_id: strawberry.Private[int]
@strawberry.field
async def author(self) -> User:
return await user_loader.load(self.author_id)
Authentication in GraphQL
from strawberry.types import Info
from fastapi import Depends
async def get_context(current_user=Depends(get_current_user)):
return {"user": current_user}
@strawberry.type
class Query:
@strawberry.field
def me(self, info: Info) -> User:
user = info.context["user"]
if not user:
raise Exception("Not authenticated")
return user
app.include_router(
GraphQLRouter(schema, context_getter=get_context),
prefix="/graphql"
)
Key Takeaways
- GraphQL shines when clients need flexible data fetching
- Strawberry provides a Pythonic, type-safe GraphQL experience
- Use DataLoaders to prevent N+1 query problems
- Mutations handle writes, queries handle reads
- Authentication works through context injection
6. GraphQL playground at /graphql gives you interactive docs for free
🚀 Level up your AI workflow! Check out my AI Developer Mega Prompt Pack — 80 battle-tested prompts for developers. $9.99
Top comments (0)