Home Python GraphQL in Python with Strawberry: A Practical Guide
Advanced 3 min · July 14, 2026

GraphQL in Python with Strawberry: A Practical Guide

Learn to build production-ready GraphQL APIs in Python using Strawberry.

N
Naren Founder & Principal Engineer

20+ years shipping production Python across data and backend systems. Drawn from code that ran under real load.

Follow
Production
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
Before you start⏱ 20-25 min read
  • Basic knowledge of Python (functions, classes, type hints)
  • Familiarity with GraphQL concepts (queries, mutations, subscriptions)
  • Python 3.8+ installed
  • Basic understanding of async/await
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • Strawberry is a modern Python library for building GraphQL APIs with type hints.
  • Define schema using Python classes decorated with @strawberry.type.
  • Resolvers are methods on types; mutations use @strawberry.mutation.
  • Supports subscriptions via async generators.
  • Integrates with Django, Flask, FastAPI, and ASGI servers.
✦ Definition~90s read
What is GraphQL in Python with Strawberry?

Strawberry is a modern Python library that leverages type hints to build GraphQL APIs declaratively.

Imagine you're at a restaurant.
Plain-English First

Imagine you're at a restaurant. REST is like ordering from a fixed menu: you get a full plate even if you only want a side. GraphQL is like a custom buffet where you tell the chef exactly which ingredients you want. Strawberry is the chef's assistant that helps you prepare that custom meal in Python.

GraphQL has revolutionized API development by giving clients the power to request exactly the data they need. In the Python ecosystem, Strawberry stands out as a modern, type-hint-driven library that makes building GraphQL APIs intuitive and safe. Unlike older libraries like Graphene, Strawberry leverages Python's type hints to define schemas, reducing boilerplate and catching errors early.

In this tutorial, you'll learn how to build a production-ready GraphQL API with Strawberry. We'll cover schema design, resolvers, mutations, subscriptions, authentication, and error handling. You'll see real-world patterns like pagination, N+1 query optimization, and integration with FastAPI. By the end, you'll be able to confidently build and debug GraphQL APIs in Python.

Whether you're building a new microservice or adding a GraphQL layer to an existing Django or Flask app, Strawberry provides the tools you need. Let's dive in.

Setting Up Strawberry

First, install Strawberry and an ASGI server like Uvicorn. We'll also install strawberry-graphql-django if using Django, but for this tutorial we'll use FastAPI.

``bash pip install strawberry-graphql fastapi uvicorn ``

Create a file schema.py and define your first type. Strawberry uses Python dataclasses with decorators.

schema.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import strawberry

@strawberry.type
class Book:
    title: str
    author: str

@strawberry.type
class Query:
    @strawberry.field
    def books(self) -> list[Book]:
        return [Book(title="1984", author="George Orwell")]

schema = strawberry.Schema(query=Query)
🔥Type Hints Are Mandatory
📊 Production Insight
Always use strawberry.Schema(query=Query, mutation=Mutation) to expose your schema. For production, consider using strawberry.tools.create_schema for more control.
🎯 Key Takeaway
Define your GraphQL schema using Python classes and type hints. The @strawberry.type decorator marks a class as a GraphQL object type.

Resolvers and Field Logic

Resolvers are functions that return data for a field. In Strawberry, you can attach resolvers directly to fields using @strawberry.field. You can also define resolver functions separately.

Let's expand our schema with a resolver that accepts arguments.

schema.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import strawberry
from typing import Optional

@strawberry.type
class Book:
    title: str
    author: str
    @strawberry.field
    def summary(self) -> str:
        return f"{self.title} by {self.author}"

@strawberry.type
class Query:
    @strawberry.field
    def books(self, author: Optional[str] = None) -> list[Book]:
        all_books = [
            Book(title="1984", author="George Orwell"),
            Book(title="Brave New World", author="Aldous Huxley"),
        ]
        if author:
            return [b for b in all_books if b.author == author]
        return all_books
💡Use Optional for Nullable Arguments
📊 Production Insight
For complex resolvers, consider using strawberry.field(resolver=my_function) to keep types clean. Always validate inputs.
🎯 Key Takeaway
Resolvers can be methods on the type or standalone functions. Use arguments to filter or parameterize queries.

Mutations: Changing Data

Mutations are used to modify data. In Strawberry, you define a mutation class with @strawberry.type and mark methods with @strawberry.mutation. Mutations often return a custom response type to include errors.

Let's create a simple mutation to add a book.

schema.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import strawberry
from typing import List

@strawberry.type
class Book:
    title: str
    author: str

# In-memory storage for demo
books_db: List[Book] = []

@strawberry.input
class BookInput:
    title: str
    author: str

@strawberry.type
class Mutation:
    @strawberry.mutation
    def add_book(self, book: BookInput) -> Book:
        new_book = Book(title=book.title, author=book.author)
        books_db.append(new_book)
        return new_book

schema = strawberry.Schema(query=Query, mutation=Mutation)
⚠ Input Types vs Object Types
📊 Production Insight
For production, use a Union of success and error types to handle failures gracefully. Example: Union[Book, AddBookError].
🎯 Key Takeaway
Mutations use @strawberry.mutation and often accept input types. Always return a meaningful response, possibly including error information.

Subscriptions: Real-Time Updates

Subscriptions allow clients to receive real-time updates via WebSockets. Strawberry supports subscriptions using async generators. You need an ASGI server that supports WebSockets, like Uvicorn.

Let's create a simple subscription that emits a new book every 5 seconds.

schema.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
import strawberry
import asyncio
from typing import AsyncGenerator

@strawberry.type
class Subscription:
    @strawberry.subscription
    async def book_added(self) -> AsyncGenerator[Book, None]:
        while True:
            await asyncio.sleep(5)
            yield Book(title="New Book", author="AI")

schema = strawberry.Schema(query=Query, mutation=Mutation, subscription=Subscription)
🔥Async Only
📊 Production Insight
In production, use a message broker like Redis to broadcast events across multiple server instances. Strawberry integrates with strawberry.channels for Django.
🎯 Key Takeaway
Subscriptions use @strawberry.subscription and async generators. They require WebSocket support in your server.

Integrating with FastAPI

Strawberry provides an integration with FastAPI via strawberry.fastapi.GraphQLRouter. This allows you to add a GraphQL endpoint to your FastAPI app with minimal setup.

Let's create a FastAPI app that serves our schema.

main.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
from fastapi import FastAPI
from strawberry.fastapi import GraphQLRouter
from schema import schema

app = FastAPI()

graphql_app = GraphQLRouter(schema)

app.include_router(graphql_app, prefix="/graphql")

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)
💡GraphQL Playground
📊 Production Insight
For production, enable CORS, add authentication middleware, and set query depth limits. Use strawberry.extensions for logging and metrics.
🎯 Key Takeaway
Strawberry integrates seamlessly with FastAPI. Use GraphQLRouter to add a GraphQL endpoint.

Authentication and Context

GraphQL APIs often need authentication. Strawberry allows you to pass a context object to resolvers. You can set up authentication in the context and access it in resolvers.

Let's add a simple token-based authentication.

main.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
from fastapi import FastAPI, Request, HTTPException
from strawberry.fastapi import GraphQLRouter
from schema import schema

async def get_context(request: Request):
    token = request.headers.get("Authorization")
    if token != "secret-token":
        raise HTTPException(status_code=401, detail="Unauthorized")
    return {"user": "admin"}

app = FastAPI()
graphql_app = GraphQLRouter(schema, context_getter=get_context)
app.include_router(graphql_app, prefix="/graphql")
⚠ Never Hardcode Tokens
📊 Production Insight
For fine-grained permissions, use Strawberry's @strawberry.permission classes. They allow you to check permissions before resolver execution.
🎯 Key Takeaway
Use context_getter to inject authentication data into resolvers. Access it via info.context.

Error Handling and Unions

GraphQL errors should be communicated clearly. Instead of returning null, use Union types to return either success or error objects. This gives clients structured error information.

Let's create a mutation that can fail.

schema.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import strawberry
from typing import Union

@strawberry.type
class AddBookSuccess:
    book: Book

@strawberry.type
class AddBookError:
    message: str

AddBookResult = Union[AddBookSuccess, AddBookError]

@strawberry.type
class Mutation:
    @strawberry.mutation
    def add_book(self, book: BookInput) -> AddBookResult:
        if not book.title:
            return AddBookError(message="Title cannot be empty")
        new_book = Book(title=book.title, author=book.author)
        books_db.append(new_book)
        return AddBookSuccess(book=new_book)
💡Union Types in GraphQL
📊 Production Insight
Consider using a generic Error type with a code and message for consistency across your API.
🎯 Key Takeaway
Use Union types for mutation results to provide structured error handling. This follows GraphQL best practices.
● Production incidentPOST-MORTEMseverity: high

The N+1 Query Nightmare

Symptom
A GraphQL query for a list of authors and their books took 30 seconds to respond, causing timeouts.
Assumption
The developer assumed GraphQL would automatically batch database queries.
Root cause
Each author's books resolver executed a separate SQL query (N+1 problem).
Fix
Implemented DataLoader to batch and cache database queries per request.
Key lesson
  • Always use DataLoader for related fields to avoid N+1 queries.
  • Profile your GraphQL resolvers with tools like Apollo Studio or Strawberry's built-in extensions.
  • Set query depth and complexity limits to prevent abusive queries.
  • Use connection-based pagination (Relay spec) for list fields.
Production debug guideSymptom to Action4 entries
Symptom · 01
Query returns null for a field
Fix
Check resolver returns correct value; verify field name matches schema; enable debug mode for stack trace.
Symptom · 02
Mutation fails silently
Fix
Add error handling in resolver; use Union types for error responses; check database constraints.
Symptom · 03
Slow query execution
Fix
Enable Strawberry's query profiler; check for N+1 queries; add DataLoader; consider pagination limits.
Symptom · 04
Subscription not receiving updates
Fix
Verify WebSocket connection; check channel layer configuration; ensure async generator yields correctly.
★ Quick Debug Cheat SheetCommon GraphQL issues and immediate actions.
Field returns null unexpectedly
Immediate action
Check resolver return value
Commands
print(result) in resolver
strawberry debug schema
Fix now
Add error logging and return default value
Mutation returns error+
Immediate action
Check input types
Commands
print(info.context)
strawberry export-schema
Fix now
Use Union[Success, Error] return type
Subscription not working+
Immediate action
Check WebSocket endpoint
Commands
curl -H 'Connection: Upgrade' -H 'Upgrade: websocket' ...
strawberry subscription test
Fix now
Ensure async generator is correct
FeatureStrawberryGraphene
Type hintsNative supportRequires manual schema
PerformanceFast, async-nativeSlower, sync by default
Django integrationFirst-class via strawberry-graphql-djangoMature django-graphene
Learning curveLow (uses Python types)Moderate (SDL-like syntax)
SubscriptionsBuilt-in async generatorsVia channels
⚙ Quick Reference
7 commands from this guide
FileCommand / CodePurpose
schema.py@strawberry.typeSetting Up Strawberry
schema.pyfrom typing import OptionalResolvers and Field Logic
schema.pyfrom typing import ListMutations
schema.pyfrom typing import AsyncGeneratorSubscriptions
main.pyfrom fastapi import FastAPIIntegrating with FastAPI
main.pyfrom fastapi import FastAPI, Request, HTTPExceptionAuthentication and Context
schema.pyfrom typing import UnionError Handling and Unions

Key takeaways

1
Strawberry uses Python type hints to define GraphQL schemas, making code readable and type-safe.
2
Always use DataLoader to avoid N+1 queries in list fields.
3
Use Union types for mutation results to provide structured error handling.
4
Integrate authentication via context getters and permission classes.
5
Test your schema with Strawberry's test client for reliability.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
What is the N+1 problem in GraphQL and how do you solve it?
Q02SENIOR
How do you implement authentication in Strawberry?
Q03JUNIOR
Explain the difference between `@strawberry.type` and `@strawberry.input...
Q04SENIOR
How do you handle errors in GraphQL mutations?
Q05SENIOR
What are subscriptions and how do they work in Strawberry?
Q01 of 05SENIOR

What is the N+1 problem in GraphQL and how do you solve it?

ANSWER
The N+1 problem occurs when a resolver for a list field executes a separate database query for each item. It's solved using DataLoader, which batches queries and caches results per request.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
How do I handle file uploads in Strawberry?
02
Can I use Strawberry with Django?
03
How do I implement pagination?
04
What is DataLoader and how do I use it?
05
How do I test Strawberry schemas?
N
Naren Founder & Principal Engineer

20+ years shipping production Python across data and backend systems. Drawn from code that ran under real load.

Follow
Verified
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
🔥

That's Python Libraries. Mark it forged?

3 min read · try the examples if you haven't

Previous
SciPy: Scientific Computing in Python
64 / 69 · Python Libraries
Next
SQLModel: Python ORM for FastAPI and SQLAlchemy