I’ve been thinking about APIs a lot lately. You know the feeling—you build a clean, well-documented REST endpoint, only to hear your frontend team ask, “Can we get just one more field?” or “We’re making five calls here, can we combine them?” These small requests pile up, slowing down development for everyone. This constant back-and-forth is what pushed me to look for a better way to build and connect software. That’s how I arrived at GraphQL, and specifically, at combining two fantastic Python tools: Strawberry and FastAPI.
So, what makes this combination so powerful for a production system? Think of it like this. Traditional REST APIs are like fixed menus at a restaurant—you get the appetizer, main, and dessert as a set. GraphQL, on the other hand, is a buffet where your client can put exactly what they want on their plate. Strawberry GraphQL uses Python’s own type hints to define your data buffet in a way that feels natural. FastAPI then serves it with incredible speed and automatic, interactive documentation. Have you ever wished you could ask for exactly the data you need in a single request? That’s the shift we’re talking about.
Let’s start by setting up the environment. I like to keep things organized from the beginning.
mkdir my_graphql_api && cd my_graphql_api
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
pip install fastapi strawberry-graphql[fastapi] uvicorn sqlalchemy asyncpg
Next, we define what our data looks like. This is where Strawberry’s clarity shines. We use simple Python classes with type annotations. Imagine we’re building a small book catalog.
import strawberry
from typing import List, Optional
@strawberry.type
class Author:
id: strawberry.ID
name: str
# This connects to the Book type below
books: List["Book"]
@strawberry.type
class Book:
id: strawberry.ID
title: str
year: int
# This creates a link back to the author
author: Author
With our types defined, we need a way for clients to ask for this data. This is done through a Query class. Each function inside it, marked with @strawberry.field, is a possible entry point for a query.
@strawberry.type
class Query:
@strawberry.field
def book(self, id: strawberry.ID) -> Optional[Book]:
# In reality, you'd fetch from a database here
if id == "1":
return Book(id="1", title="The Great Gatsby", year=1925, author=Author(id="10", name="F. Scott Fitzgerald", books=[]))
return None
@strawberry.field
def all_authors(self) -> List[Author]:
# Simulating a list of authors
return [Author(id="10", name="F. Scott Fitzgerald", books=[])]
But what about changing data? That’s where Mutations come in. They handle create, update, and delete operations. See how the input is clearly defined with @strawberry.input?
@strawberry.input
class BookInput:
title: str
year: int
author_id: strawberry.ID
@strawberry.type
class Mutation:
@strawberry.mutation
def add_book(self, book_data: BookInput) -> Book:
# Here you would save the book to your database
print(f"Adding book: {book_data.title}")
# Return the newly created book object
new_book = Book(
id="99",
title=book_data.title,
year=book_data.year,
author=Author(id=book_data.author_id, name="", books=[])
)
return new_book
Now, we bring it all together with FastAPI. This single piece of code gives you a full, working GraphQL endpoint with a built-in explorer called GraphiQL.
from fastapi import FastAPI
from strawberry.fastapi import GraphQLRouter
# Combine our Query and Mutation definitions
schema = strawberry.Schema(query=Query, mutation=Mutation)
graphql_app = GraphQLRouter(schema)
app = FastAPI()
app.include_router(graphql_app, prefix="/graphql")
# To run: uvicorn main:app --reload
If you run this app and go to http://localhost:8000/graphql, you’ll see the GraphiQL interface. Try pasting this query on the left side. Notice how you request only the id and title, not the year or author details. The API gives you precisely that.
query {
book(id: "1") {
id
title
}
}
You can also run the mutation we created.
mutation {
addBook(bookData: {title: "New Book", year: 2023, authorId: "10"}) {
id
title
}
}
A common question is: how do you prevent a single query from asking for too much data and overloading your database? This is a critical consideration for production. Strawberry has built-in tools for this. You can add a cost analysis to your schema to reject overly complex queries before they are executed. It’s like having a bouncer for your API.
What about fetching related data efficiently? If a query asks for 100 books and their authors, a simple approach might make 101 separate database calls (1 for the books, then 1 for each author). This is known as the N+1 problem. The solution is a DataLoader, which batches those requests automatically. While we won’t implement it fully here, know that Strawberry supports this pattern beautifully to keep your API fast.
Finally, adding authentication is straightforward. FastAPI’s dependency injection system works seamlessly with Strawberry. You can create a dependency to verify a user’s token and make that user information available inside every resolver function.
Building an API this way changes the conversation between frontend and backend developers. It becomes collaborative, focused on the data shape rather than endless endpoint negotiations. It gives frontend teams the independence to build faster, while ensuring backend data remains consistent and well-structured.
I hope this guide helps you start building more flexible and efficient APIs. This approach has certainly streamlined projects I’ve worked on. What’s the first data model you would build with this setup? Share your thoughts in the comments below—I’d love to hear what you’re working on. If you found this useful, please like and share it with other developers who might be wrestling with their API design.