Building High-Performance GraphQL APIs with Python and Strawberry

Python tutorial - IT technology blog
Python tutorial - IT technology blog

Why Choose GraphQL and Strawberry over REST?

If you’ve ever built REST APIs for real-world projects, you’ve likely encountered a scenario where the frontend needs to display a user’s name along with their three latest posts. With REST, you usually have to call two separate APIs or modify the /user endpoint to cram in extra post data. The first approach wastes network round-trips, while the second makes the API bloated and hard to maintain.

GraphQL solves this problem by allowing clients to define exactly what data they need. Previously, implementing GraphQL in Python was quite cumbersome with Graphene. However, after switching to Strawberry, things became much easier. This library fully leverages modern Python Type Hints, making code clean, easy to debug, and providing excellent support for IDEs like VS Code or PyCharm.

In a dashboard project handling about 2,000 requests per second, combining Strawberry and FastAPI helped me reduce development time by 30%. Instead of defining dozens of small REST endpoints, I could focus solely on the Schema. System performance was also maintained thanks to the asynchronous mechanism supported by both libraries.

Environment Setup

You should use Python 3.9 or higher to make the most of type-related features. We will install FastAPI as the web framework and the FastAPI-supported version of Strawberry.

# Initialize virtual environment
python -m venv venv
source venv/bin/activate

# Install libraries
pip install "strawberry-graphql[fastapi]" uvicorn fastapi

I’m also installing uvicorn to serve as the ASGI server. This is the standard choice for running async Python applications in production environments today.

Schema Design: Thinking in Types

In the world of GraphQL, the Schema is the most critical component. Instead of worrying about URLs, we define “Types”. Suppose you’re building a book management app; start with the @strawberry.type decorator.

import strawberry
from typing import List, Optional

@strawberry.type
class Book:
    id: int
    title: str
    author: str
    price: float

# Mock data
BOOKS_DB = [
    Book(id=1, title="Python Programming", author="IT Admin", price=150.0),
    Book(id=2, title="GraphQL Basics", author="Strawberry Fan", price=200.0),
]

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

    @strawberry.field
    def book_by_id(self, id: int) -> Optional[Book]:
        return next((book for book in BOOKS_DB if book.id == id), None)

A major advantage of Strawberry is its use of pure Python Type Hints. You don’t need to learn a new syntax for type declarations. If you’re already familiar with Pydantic, picking up Strawberry will take only a few minutes.

Integrating with FastAPI

Once the Schema is ready, you need to expose it on an endpoint for client access. Strawberry provides GraphQLRouter, making FastAPI integration incredibly simple.

from fastapi import FastAPI
from strawberry.fastapi import GraphQLRouter

# Initialize Schema
schema = strawberry.Schema(query=Query)
graphql_app = GraphQLRouter(schema)

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

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

Run the file and visit http://localhost:8000/graphql to see the GraphiQL interface. Here, you can test queries directly without needing Postman or Insomnia.

Solving the N+1 Problem for Performance Optimization

The N+1 error is a silent performance killer in GraphQL. For example, when fetching a list of 10 books, if you call the database once for each book to retrieve the author’s name, the system executes a total of 11 queries. This is extremely resource-intensive.

The best solution is using a DataLoader. This mechanism batches all required IDs and performs a single SQL query with a WHERE id IN (...) condition.

from strawberry.dataloader import DataLoader

async def load_authors(keys: List[int]) -> List[str]:
    # Execute only one query for all keys
    author_map = {1: "John Doe", 2: "Jane Smith"}
    return [author_map.get(key, "Unknown") for key in keys]

author_loader = DataLoader(load_fn=load_authors)

@strawberry.type
class BookWithLoader:
    id: int
    author_id: int
    
    @strawberry.field
    async def author_name(self) -> str:
        return await author_loader.load(self.author_id)

Applying DataLoader helped me reduce the latency of a report page from 5 seconds to less than 200ms. This is a must-know technique if you want to work with large datasets.

Monitoring and Operations

How do you know if your API is performing well? With FastAPI, you should add middleware to log the execution time of each request. Strawberry also supports Extensions for integrating Apollo Tracing or Sentry.

Never push an API to production without monitoring tools. Tracking which queries are consuming resources will help you optimize the system in time. The combination of Strawberry’s strictness and FastAPI’s speed creates a powerful backend system that makes frontend teams much more efficient.

Share: