Strawberry is a Python library that lets you build GraphQL APIs using Python type hints. It integrates seamlessly with FastAPI, allowing you to combine traditional REST endpoints with a GraphQL interface within the same application.
Let’s see it in action. Imagine we have a simple FastAPI app with a few REST endpoints. We want to add a GraphQL API that can query the same data.
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List
app = FastAPI()
# Sample data
books_db = {
1: {"title": "The Hitchhiker's Guide to the Galaxy", "author_id": 1},
2: {"title": "The Restaurant at the End of the Universe", "author_id": 1},
3: {"title": "Pride and Prejudice", "author_id": 2},
}
authors_db = {
1: {"name": "Douglas Adams"},
2: {"name": "Jane Austen"},
}
class Book(BaseModel):
id: int
title: str
author_id: int
class Author(BaseModel):
id: int
name: str
@app.get("/books", response_model=List[Book])
async def get_books():
return [Book(id=k, **v) for k, v in books_db.items()]
@app.get("/books/{book_id}", response_model=Book)
async def get_book(book_id: int):
return Book(id=book_id, **books_db[book_id])
@app.get("/authors", response_model=List[Author])
async def get_authors():
return [Author(id=k, **v) for k, v in authors_db.items()]
@app.get("/authors/{author_id}", response_model=Author)
async def get_author(author_id: int):
return Author(id=author_id, **authors_db[author_id])
Now, let’s add GraphQL using Strawberry. First, install Strawberry:
pip install strawberry FastAPI uvicorn
We’ll define our GraphQL schema using Strawberry’s decorators and Python types.
import strawberry
from typing import List, Optional
# Define your GraphQL types
@strawberry.type
class Book:
id: int
title: str
author: 'Author' # Forward reference
@strawberry.type
class Author:
id: int
name: str
books: List[Book] # This will be resolved lazily
# Data resolvers (these will be called by Strawberry)
def get_all_books() -> List[Book]:
return [Book(id=k, title=v["title"], author=get_author_by_id(v["author_id"])) for k, v in books_db.items()]
def get_book_by_id(id: int) -> Optional[Book]:
if id in books_db:
book_data = books_db[id]
return Book(id=id, title=book_data["title"], author=get_author_by_id(book_data["author_id"]))
return None
def get_all_authors() -> List[Author]:
return [Author(id=k, name=v["name"], books=get_books_by_author_id(k)) for k, v in authors_db.items()]
def get_author_by_id(id: int) -> Optional[Author]:
if id in authors_db:
author_data = authors_db[id]
return Author(id=id, name=author_data["name"], books=get_books_by_author_id(id))
return None
def get_books_by_author_id(author_id: int) -> List[Book]:
return [
Book(id=k, title=v["title"], author=get_author_by_id(v["author_id"]))
for k, v in books_db.items() if v["author_id"] == author_id
]
# Define your GraphQL queries
@strawberry.type
class Query:
@strawberry.field
def books(self) -> List[Book]:
return get_all_books()
@strawberry.field
def book(self, id: int) -> Optional[Book]:
return get_book_by_id(id)
@strawberry.field
def authors(self) -> List[Author]:
return get_all_authors()
@strawberry.field
def author(self, id: int) -> Optional[Author]:
return get_author_by_id(id)
# Create the Strawberry schema
schema = strawberry.Schema(query=Query)
Now, integrate this schema into your FastAPI app. Strawberry provides a GraphQLRouter for this.
from fastapi import FastAPI
from strawberry.fastapi import GraphQLRouter
from typing import List, Optional # Import these if not already imported
# ... (previous FastAPI app and Strawberry schema definitions) ...
# Create the GraphQL router
graphql_app = GraphQLRouter(schema)
# Mount the GraphQL router to your FastAPI app
app.include_router(graphql_app, prefix="/graphql")
# You can still keep your existing REST endpoints
@app.get("/rest/hello")
async def hello_rest():
return {"message": "Hello from REST!"}
# Run the app with uvicorn
# uvicorn main:app --reload
When you run this application, you’ll have access to a GraphQL endpoint at /graphql. You can use tools like GraphiQL (which Strawberry automatically includes) to explore your schema and send queries.
For example, to fetch the title of all books and the name of their authors, you’d send this GraphQL query to /graphql:
query {
authors {
name
books {
title
}
}
}
The response would look like:
{
"data": {
"authors": [
{
"name": "Douglas Adams",
"books": [
{
"title": "The Hitchhiker's Guide to the Galaxy"
},
{
"title": "The Restaurant at the End of the Universe"
}
]
},
{
"name": "Jane Austen",
"books": [
{
"title": "Pride and Prejudice"
}
]
}
]
}
}
The surprising part is how Strawberry handles the relationship between Book and Author. When you query Author.books, Strawberry doesn’t immediately try to fetch all books for every author. Instead, it uses lazy loading. The books field on the Author type is a List[Book]. Strawberry knows that to resolve this, it needs to call the get_books_by_author_id function, and it only does so when that specific field is requested in the GraphQL query. This prevents over-fetching and N+1 query problems by default, as it intelligently groups requests for related data.
This lazy loading is key to understanding how Strawberry performs. When a field like author on a Book is requested, Strawberry looks for a resolver that can provide an Author object for that specific book. It doesn’t fetch all authors; it uses the context of the book it’s currently resolving to find the relevant author. This is achieved by passing the author field a function that takes the Book instance implicitly and uses its author_id to fetch the correct author from authors_db.
The next step is often to explore mutations, allowing you to create, update, or delete data through your GraphQL API.