Strawberry GraphQL Tutorial: Build Type-Safe APIs in Python

Strawberry is a modern Python library that lets you build GraphQL APIs using nothing more than standard Python type hints and dataclass-style decorators. Instead of writing a separate schema definition language, you define your types as Python classes and let Strawberry generate the GraphQL schema for you. The result is cleaner code, full IDE autocomplete, and type checking that catches bugs before they reach production.

GraphQL gives clients the ability to request exactly the data they need from a single endpoint, eliminating the over-fetching and under-fetching problems that are common with REST APIs. Python has several libraries for building GraphQL servers, but Strawberry has quickly become the recommended choice for new projects, particularly when paired with FastAPI. The library requires Python 3.10 or higher and is actively maintained with frequent releases.

What Is Strawberry and Why Use It

Strawberry is a code-first GraphQL framework for Python. "Code-first" means you write Python classes and functions, and Strawberry turns them into a valid GraphQL schema automatically. This is the opposite of the "schema-first" approach (used by libraries like Ariadne) where you write the GraphQL schema definition language (SDL) by hand and then wire it to Python resolver functions.

The key advantage of Strawberry's approach is that your Python types and your GraphQL types are the same thing. There is no separate SDL file to keep in sync, which eliminates a whole category of bugs where the schema and the code drift apart over time. Your IDE can provide autocomplete and inline error checking because everything is expressed through standard type annotations.

Note

Strawberry requires Python 3.10 or newer. The latest release as of March 2026 is version 0.308.3, and it is licensed under the MIT license. The project has over 4,600 stars on GitHub and is in active development.

Strawberry ships with native support for async/await, generics, schema extensions, Apollo Federation, and DataLoaders for batching database queries. It also includes integrations for FastAPI, Django, Flask, Litestar, AIOHTTP, and several other frameworks right out of the box.

Installation and Your First Schema

Getting started with Strawberry takes just a few commands. You will want to create a virtual environment first, then install Strawberry along with the integration for your chosen web framework. For this tutorial, we will use FastAPI.

# Create and activate a virtual environment
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install Strawberry with FastAPI support
pip install strawberry-graphql[fastapi] uvicorn

With Strawberry installed, you can define your first GraphQL schema in a file called schema.py. Every Strawberry schema starts with type definitions decorated using @strawberry.type and a root Query class that defines what data clients can request.

import strawberry
from typing import List


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


def get_books() -> List[Book]:
    return [
        Book(title="Automate the Boring Stuff", author="Al Sweigart", year=2019),
        Book(title="Fluent Python", author="Luciano Ramalho", year=2022),
        Book(title="Python Crash Course", author="Eric Matthes", year=2023),
    ]


@strawberry.type
class Query:
    @strawberry.field
    def books(self) -> List[Book]:
        return get_books()


schema = strawberry.Schema(query=Query)

The Book class looks like a standard Python dataclass, and that is intentional. Strawberry is built directly on top of Python's dataclass machinery. Each attribute becomes a GraphQL field, and the Python type annotation (like str or int) maps to the corresponding GraphQL scalar type automatically.

Pro Tip

You can test your schema immediately using Strawberry's built-in development server by running strawberry server schema from the command line. This opens GraphiQL in your browser at http://localhost:8000/graphql where you can write and test queries interactively.

Once the server is running, you can send a query like this through GraphiQL to fetch only the fields you need:

# This is a GraphQL query, not Python
{
  books {
    title
    author
  }
}

The response will include only the title and author fields for each book, without the year field. This is the core value of GraphQL: clients decide exactly which fields they want, and the server returns nothing extra.

Queries, Mutations, and Subscriptions

A complete GraphQL API typically has three entry points. Queries fetch data without changing anything on the server. Mutations create, update, or delete data. Subscriptions stream real-time data to the client over a WebSocket connection. Strawberry supports all three.

Writing a Mutation

Mutations in Strawberry work the same way as queries, but they are defined in a separate class and decorated with @strawberry.mutation. Here is an example that adds a new book to the collection:

@strawberry.type
class Mutation:
    @strawberry.mutation
    def add_book(self, title: str, author: str, year: int) -> Book:
        new_book = Book(title=title, author=author, year=year)
        # In a real application, save to database here
        return new_book


schema = strawberry.Schema(query=Query, mutation=Mutation)

To call this mutation from a client, you would send a document like this:

# GraphQL mutation
mutation {
  addBook(title: "Effective Python", author: "Brett Slatkin", year: 2024) {
    title
    author
  }
}

Notice that Strawberry automatically converts the Python snake_case method name add_book to the GraphQL camelCase convention addBook. This is handled transparently so you can keep writing idiomatic Python.

Note

For more complex mutations, Strawberry provides an InputMutationExtension that automatically generates a dedicated input type from your resolver's parameters. This follows the GraphQL best practice of using a single input argument for mutations.

Writing a Subscription

Subscriptions allow your server to push updates to clients in real time. In Strawberry, a subscription resolver is an async generator function. The server must support ASGI and WebSockets for subscriptions to work.

import asyncio
from typing import AsyncGenerator


@strawberry.type
class Subscription:
    @strawberry.subscription
    async def book_added(self) -> AsyncGenerator[Book, None]:
        # In production, listen to a message broker like Redis
        while True:
            await asyncio.sleep(5)
            yield Book(
                title="New Release",
                author="Unknown",
                year=2026
            )


schema = strawberry.Schema(
    query=Query,
    mutation=Mutation,
    subscription=Subscription
)

The return type AsyncGenerator[Book, None] tells Strawberry that this resolver will yield Book objects over time. In a production application, you would typically listen to a message queue or database change stream rather than using a simple sleep loop.

Integrating with FastAPI

FastAPI is the recommended web framework for serving Strawberry schemas. Strawberry provides a dedicated GraphQLRouter that plugs directly into FastAPI's routing system, giving you access to dependency injection, middleware, CORS configuration, and automatic OpenAPI documentation for any REST endpoints alongside your GraphQL endpoint.

from fastapi import FastAPI
from strawberry.fastapi import GraphQLRouter

# Import your schema from the schema module
from schema import schema

app = FastAPI()

graphql_router = GraphQLRouter(schema)
app.include_router(graphql_router, prefix="/graphql")


if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

With this setup, your GraphQL endpoint is available at /graphql and the interactive GraphiQL playground loads automatically in the browser at the same URL. FastAPI handles HTTP requests for queries and mutations, and WebSocket connections for subscriptions, all through that single endpoint.

Pro Tip

Strawberry also supports Django, Flask, Litestar, Sanic, Chalice, and AIOHTTP. If you are adding GraphQL to an existing Django project, the strawberry-graphql-django extension can introspect your Django models and generate types automatically.

Adding Context

In a real application, your resolvers need access to things like database sessions, the current authenticated user, and DataLoaders. Strawberry handles this through a context object that you can customize when setting up the router:

from strawberry.fastapi import GraphQLRouter
from strawberry.types import Info


async def get_context():
    return {
        "db": await get_database_session(),
        "current_user": None,  # Set from auth middleware
    }


graphql_router = GraphQLRouter(
    schema,
    context_getter=get_context
)


# Then in your resolver:
@strawberry.type
class Query:
    @strawberry.field
    async def my_books(self, info: Info) -> List[Book]:
        db = info.context["db"]
        user = info.context["current_user"]
        return await db.get_books_for_user(user.id)

The info parameter is injected automatically by Strawberry into any resolver that declares it. Through info.context, you can access the entire request context including database connections, authentication state, and DataLoader instances.

Solving the N+1 Problem with DataLoaders

One of the common performance pitfalls in GraphQL is the N+1 query problem. If you have a list of 100 posts and each post has an author, a naive implementation would run 1 query to fetch the posts and then 100 separate queries to fetch each author. DataLoaders solve this by batching those 100 individual author lookups into a single database query.

Strawberry includes a built-in DataLoader class. You define a batch loading function that accepts a list of keys and returns the corresponding results, and the DataLoader handles the rest:

from strawberry.dataloader import DataLoader
from typing import List


async def load_users(keys: List[int]) -> List[User]:
    # Single database query for all requested users
    users = await db.fetch_users_by_ids(keys)
    # Return results in the same order as the keys
    user_map = {u.id: u for u in users}
    return [user_map.get(key) for key in keys]


# Create a fresh DataLoader per request in your context
async def get_context():
    return {
        "user_loader": DataLoader(load_fn=load_users),
    }


# Use it in a resolver
@strawberry.type
class Post:
    title: str
    author_id: int

    @strawberry.field
    async def author(self, info: Info) -> User:
        return await info.context["user_loader"].load(self.author_id)
Warning

Always create a new DataLoader instance for each request (inside your context factory), not once at module level. DataLoaders cache their results by default, and sharing a single instance across requests would serve stale data after mutations.

When the client requests post.author for each post in a list, the DataLoader collects all the author_id values and fires a single call to load_users with the full batch. This turns 100 queries into 1, which is a significant performance gain on any non-trivial dataset.

Strawberry vs. Graphene vs. Ariadne

Python has three well-known GraphQL libraries, each with a different philosophy. Understanding the differences helps you choose the right tool for your project.

Feature Strawberry Graphene Ariadne
Approach Code-first (type hints) Code-first (custom classes) Schema-first (SDL)
Python Version 3.10+ 3.8+ 3.8+
Type Safety Full (native type hints) Partial Minimal
Boilerplate Low Medium to High Low
FastAPI Integration Built-in Third-party ASGI support
Active Development Very active Slow Moderate
Federation Support Built-in Third-party Third-party

Graphene was the first major GraphQL library for Python and still has a large user base. However, its development pace has slowed considerably, and the API requires more boilerplate compared to Strawberry. Graphene uses custom class hierarchies (like graphene.ObjectType) rather than native Python type hints, which means your IDE cannot provide the same level of autocomplete and error checking.

Ariadne takes the opposite approach from both Strawberry and Graphene. Instead of generating a schema from code, you write the GraphQL SDL by hand and then bind Python functions to each field. This works well for teams that want to design the schema first and implement resolvers second, or teams where the frontend and backend groups want to agree on the schema as a shared contract.

For new Python projects, particularly those using FastAPI, Strawberry is the strongest choice. FastAPI's own documentation recommends Strawberry as the preferred GraphQL library because its type-hint-based design aligns closely with FastAPI's own philosophy.

Key Takeaways

  1. Type hints are the schema: Strawberry eliminates the gap between your Python code and your GraphQL schema by generating one from the other. This means fewer bugs, better IDE support, and less code to maintain.
  2. Start with queries and mutations, add subscriptions when needed: A basic Strawberry API only requires a Query class and a call to strawberry.Schema(). You can add mutations and subscriptions incrementally as your project grows.
  3. Use DataLoaders from the beginning: The N+1 problem is easy to miss during development and painful to fix in production. Creating DataLoaders as part of your request context from day one prevents performance issues before they start.
  4. FastAPI is the natural pairing: Strawberry's GraphQLRouter integrates directly with FastAPI's dependency injection, middleware, and CORS systems. You get a production-ready server with minimal configuration.
  5. Strawberry is the modern choice: With active development, built-in Federation support, async/await compatibility, and the endorsement of the FastAPI documentation, Strawberry is the go-to library for new Python GraphQL projects.

Strawberry provides a clean, Pythonic path to building GraphQL APIs. By relying on the type system that Python developers already know, it removes the learning curve of a separate schema language and keeps your codebase consistent from end to end. Whether you are building a small internal tool or a large-scale microservices architecture with federated schemas, Strawberry gives you the type safety and developer experience to move fast without breaking things.

back to articles