Building Modern GraphQL APIs with Strawberry on FastAPI and Django
Emily Parker
Product Engineer · Leapcell

Introduction
In today's data-driven world, efficient and flexible API design is paramount. Traditional REST APIs, while widely adopted, often face challenges like over-fetching or under-fetching data, leading to inefficient communication between client and server. This is where GraphQL shines, offering a powerful query language for your APIs that allows clients to request exactly the data they need, no more, no less. Python, with its versatility and rich ecosystem, has become a popular choice for backend development. When it comes to building modern GraphQL APIs in Python, especially within high-performance frameworks like FastAPI or robust ones like Django, the Strawberry library offers an elegant and Pythonic solution. This article delves into how Strawberry facilitates the creation of sophisticated GraphQL APIs, addressing common API design pitfalls and empowering developers with greater control over data interaction.
Core Concepts Explained
Before diving into the implementation details, let's establish a clear understanding of the key concepts involved:
- GraphQL: A query language for your API and a server-side runtime for executing queries using a type system you define for your data. It's an alternative to REST that allows clients to request specific data and schema.
- Strawberry: A "code-first" Python GraphQL library. This means you define your GraphQL schema directly in Python code using standard Python types and decorators. This approach often leads to better developer experience and type safety compared to schema-first approaches where you write GraphQL's Schema Definition Language (SDL) and then generate code.
- FastAPI: A modern, fast (high-performance) web framework for building APIs with Python 3.7+ based on standard Python type hints. It's known for its automatic interactive API documentation (Swagger UI/ReDoc) and excellent performance thanks to Starlette and Pydantic.
- Django: A high-level Python web framework that encourages rapid development and clean, pragmatic design. It's a "batteries-included" framework, offering an ORM, admin panel, templating engine, and more, making it suitable for complex, database-backed applications.
- Schema: In GraphQL, the schema describes the shape of your data and the operations clients can perform. It defines types, fields, and their relationships.
- Query: A read operation in GraphQL. Clients use queries to fetch data from the server.
- Mutation: A write operation in GraphQL. Clients use mutations to modify data on the server (e.g., create, update, delete).
- Resolver: A function in GraphQL that is responsible for fetching the data for a particular field in the schema. When a client requests a field, its corresponding resolver is executed.
Building GraphQL APIs with Strawberry
Strawberry's "code-first" approach simplifies schema definition by leveraging Python's type hints. This makes the schema definition feel natural and integrates well with IDE features like autocompletion and type checking.
Principle and Implementation
Strawberry works by allowing you to define your GraphQL types as Python classes decorated with @strawberry.type
. Fields within these types are defined as class attributes with type hints. Input types for mutations are defined similarly with @strawberry.input
.
The core idea is to define a Query
and Mutation
class, which will serve as the root types of your GraphQL schema. These classes will contain methods (resolvers) that clients can call.
Example with FastAPI
Let's illustrate with a simple example of managing books.
First, install the necessary libraries:
pip install fastapi "uvicorn[standard]" strawberry-graphql strawberry-graphql-fastapi
Now, define your Strawberry schema and integrate it with FastAPI:
import strawberry from fastapi import FastAPI from strawberry.asgi import GraphQL # In-memory database for simplicity BOOKS_DB = [] @strawberry.type class Book: id: strawberry.ID title: str author: str year: int @strawberry.input class BookInput: title: str author: str year: int @strawberry.type class Query: @strawberry.field def hello(self) -> str: return "Hello World" @strawberry.field def books(self) -> list[Book]: return BOOKS_DB @strawberry.field def book(self, id: strawberry.ID) -> Book | None: for book in BOOKS_DB: if book.id == id: return book return None @strawberry.type class Mutation: @strawberry.mutation def add_book(self, book_data: BookInput) -> Book: new_book = Book(id=strawberry.ID(str(len(BOOKS_DB) + 1)), **book_data.__dict__) BOOKS_DB.append(new_book) return new_book @strawberry.mutation def update_book(self, id: strawberry.ID, book_data: BookInput) -> Book | None: for i, book in enumerate(BOOKS_DB): if book.id == id: updated_book = Book(id=id, **book_data.__dict__) BOOKS_DB[i] = updated_book return updated_book return None @strawberry.mutation def delete_book(self, id: strawberry.ID) -> bool: global BOOKS_DB initial_len = len(BOOKS_DB) BOOKS_DB = [book for book in BOOKS_DB if book.id != id] return len(BOOKS_DB) < initial_len schema = strawberry.Schema(query=Query, mutation=Mutation) app = FastAPI() app.add_route("/graphql", GraphQL(schema)) # To run this: uvicorn main:app --reload
In this FastAPI example:
- We define
Book
as a SQLAlchemy type using@strawberry.type
. Query
andMutation
classes define the available operations.- Each field within
Query
andMutation
is decorated with@strawberry.field
or@strawberry.mutation
respectively, making them GraphQL exposed fields/operations. - Type hints are crucial for Strawberry to infer the GraphQL types.
GraphQL
fromstrawberry.asgi
is used to integrate the Strawberry schema into FastAPI.
You can then access the GraphQL playground typically at http://127.0.0.1:8000/graphql
to send queries and mutations.
Example Query:
query { books { id title author } }
Example Mutation:
mutation AddNewBook { addBook(bookData: {title: "The Great Gatsby", author: "F. Scott Fitzgerald", year: 1925}) { id title author } }
Example with Django
Integrating Strawberry with Django is equally straightforward, often leveraging Django Models for type definitions.
First, install the necessary libraries:
pip install django strawberry-graphql "strawberry-graphql-django>=0.9"
Let's assume you have a Django project and an app named books
. Define a Book
model:
# books/models.py from django.db import models class Book(models.Model): title = models.CharField(max_length=200) author = models.CharField(max_length=200) year = models.IntegerField() def __str__(self): return self.title
Now, define your Strawberry schema in a file like books/schema.py
:
# books/schema.py import strawberry from strawberry_django.views import GraphQLView from .models import Book # Using strawberry_django to automatically generate types from Django models @strawberry.django.type(Book) class BookType: id: strawberry.ID title: str author: str year: int @strawberry.input class BookInput: title: str author: str year: int @strawberry.type class Query: @strawberry.field def hello(self) -> str: return "Hello from Django!" @strawberry.field def books(self) -> list[BookType]: return Book.objects.all() @strawberry.field def book(self, id: strawberry.ID) -> BookType | None: try: return Book.objects.get(pk=id) except Book.DoesNotExist: return None @strawberry.type class Mutation: @strawberry.mutation def add_book(self, book_data: BookInput) -> BookType: new_book = Book.objects.create(**book_data.__dict__) return new_book @strawberry.mutation def update_book(self, id: strawberry.ID, book_data: BookInput) -> BookType | None: try: book_to_update = Book.objects.get(pk=id) for key, value in book_data.__dict__.items(): setattr(book_to_update, key, value) book_to_update.save() return book_to_update except Book.DoesNotExist: return None @strawberry.mutation def delete_book(self, id: strawberry.ID) -> bool: try: book_to_delete = Book.objects.get(pk=id) book_to_delete.delete() return True except Book.DoesNotExist: return False schema = strawberry.Schema(query=Query, mutation=Mutation)
Finally, configure your Django urls.py
to expose the GraphQL endpoint:
# myproject/urls.py (or your app's urls.py) from django.contrib import admin from django.urls import path from strawberry_django.views import GraphQLView from books.schema import schema urlpatterns = [ path("admin/", admin.site.urls), path("graphql", GraphQLView.as_view(schema=schema)), ]
To run the Django application:
python manage.py makemigrations books python manage.py migrate python manage.py runserver
The Django integration benefits from strawberry-graphql-django
, which provides helper functions like @strawberry.django.type
to automatically map Django models to GraphQL types, reducing boilerplate.
Application Scenarios
- Single-Page Applications (SPAs) and Mobile Apps: GraphQL is ideal for these applications as it allows them to fetch all necessary data in a single request, reducing network round trips and improving performance.
- Microservices Architecture: GraphQL can act as an API gateway, aggregating data from various microservices and exposing a unified API to clients.
- Public APIs: Offering a GraphQL endpoint alongside or instead of REST can give consumers more flexibility and control over the data they receive.
- Real-time Data: Strawberry supports subscriptions, which enable real-time data updates using WebSockets, suitable for chat applications, live dashboards, or notifications.
Conclusion
Strawberry offers a highly Pythonic and efficient way to build robust GraphQL APIs, integrating seamlessly with popular frameworks like FastAPI and Django. Its code-first approach, strong type hinting, and excellent community support empower developers to create flexible and performant data fetching layers. By leveraging Strawberry, developers can overcome the limitations of traditional REST APIs, providing clients with precise data fetching capabilities while maintaining a clean and maintainable backend codebase. Ultimately, Strawberry helps build future-proof APIs that are both powerful and delightful to work with.