Quick Start
In this Quick-Start, we will:
- Set up a basic pair of models with a relation between them.
- Add them to a graphql schema and serve the graph API.
- Query the graph API for model contents.
For a more advanced example of a similar setup including a set of mutations and more queries, please check the example app .
Installation
poetry add strawberry-graphql-djangopoetry add django-choices-field # Not required but recommended (Not using poetry yet? pip install strawberry-graphql-django works fine too.)
Define your application models
We’ll build an example database of fruit and their colours.
Tip
You’ll notice that for Fruit.category , we use TextChoicesField instead of TextField(choices=...) .
This allows strawberry-django to automatically use an enum in the graphQL schema, instead of
a string which would be the default behaviour for TextField.
See the choices-field integration for more information.
from django.db import modelsfrom django_choices_field import TextChoicesField
class FruitCategory(models.TextChoices): CITRUS = "citrus", "Citrus" BERRY = "berry", "Berry"
class Fruit(models.Model): """A tasty treat"""
name = models.CharField(max_length=20, help_text="The name of the fruit variety") category = TextChoicesField(choices_enum=FruitCategory, help_text="The category of the fruit") color = models.ForeignKey( "Color", on_delete=models.CASCADE, related_name="fruits", blank=True, null=True, help_text="The color of this kind of fruit", )
class Color(models.Model): """The hue of your tasty treat"""
name = models.CharField( max_length=20, help_text="The color name", ) You’ll need to make migrations then migrate:
python manage.py makemigrationspython manage.py migrate Now use the django shell, the admin, the loaddata command or whatever tool you like to load some fruits and colors. I’ve loaded a red strawberry (predictable, right?!) ready for later.
Define types
Before creating queries, you have to define a type for each model. A type is a fundamental unit of the schema
which describes the shape of the data that can be queried from the GraphQL server. Types can represent scalar values (like String, Int, Boolean, Float, and ID), enums, or complex objects that consist of many fields.
Tip
A key feature of strawberry-graphql-django is that it provides helpers to create types from django models,
by automatically inferring types (and even documentation!!) from the model fields.
See the fields guide for more information.
import strawberry_djangofrom strawberry import auto
from . import models
@strawberry_django.type(models.Fruit)class Fruit: id: auto name: auto category: auto color: "Color" # Strawberry will understand that this refers to the "Color" type that's defined below
@strawberry_django.type(models.Color)class Color: id: auto name: auto fruits: list[Fruit] # This tells strawberry about the ForeignKey to the Fruit model and how to represent the Fruit instances on that relation Build the queries and schema
Next we want to assemble the schema from its building block types.
Warning
You’ll notice a familiar statement, fruits: list[Fruit] . We already used this statement in the previous step in types.py .
Seeing it twice can be a point of confusion when you’re first getting to grips with graph and strawberry.
The purpose here is similar but subtly different. Previously, the syntax defined that it was possible to make a query that traverses within the graph, from a Color to a list of Fruits. Here, the usage defines a root query (a bit like an entrypoint into the graph).
Tip
We add the DjangoOptimizerExtension here. Don’t worry about why for now, but you’re almost certain to want it.
See the optimizer guide for more information.
import strawberryfrom strawberry_django.optimizer import DjangoOptimizerExtension
from .types import Fruit
@strawberry.typeclass Query: fruits: list[Fruit] = strawberry_django.field()
schema = strawberry.Schema( query=Query, extensions=[ DjangoOptimizerExtension, ],) Serving the API
Now we’re showing off. This isn’t enabled by default, since existing django applications will likely have model docstrings and help text that aren’t user-oriented. But if you’re starting clean (or overhauling existing docstrings and help text), setting up the following is super useful for your API users.
If you don’t set these true, you can always provide user-oriented descriptions. See the types guide and fields guide for more details.
STRAWBERRY_DJANGO = { "FIELD_DESCRIPTION_FROM_HELP_TEXT": True, "TYPE_DESCRIPTION_FROM_MODEL_DOCSTRING": True,} from django.urls import include, pathfrom strawberry.django.views import AsyncGraphQLView
from .schema import schema
urlpatterns = [ path('graphql', AsyncGraphQLView.as_view(schema=schema)),] This generates following schema:
enum FruitCategory { CITRUS BERRY}
"""A tasty treat"""type Fruit { id: ID! name: String! category: FruitCategory! color: Color}
type Color { id: ID! """ field description """ name: String! fruits: [Fruit!]}
type Query { fruits: [Fruit!]!} Using the API
Start your server with:
python manage.py runserver Then visit localhost:8000/graphql in your browser. You should see the graphql explorer being served by django. Using the interactive query tool, you can query for the fruits you added earlier:
Real-World Examples
Now that you have a basic API running, let’s explore some common real-world scenarios.
Adding Filters
Allow clients to filter fruits by category or color:
import strawberryimport strawberry_djangofrom strawberry_django.optimizer import DjangoOptimizerExtensionfrom strawberry_django import filtersfrom typing import Optional
from . import modelsfrom .types import Fruit, Color
@strawberry.typeclass Query: @strawberry_django.field def fruits( self, category: Optional[str] = None, color_name: Optional[str] = None ) -> list[Fruit]: """Get fruits with optional filtering""" queryset = models.Fruit.objects.all()
if category: queryset = queryset.filter(category=category)
if color_name: queryset = queryset.filter(color__name__icontains=color_name)
return queryset
schema = strawberry.Schema( query=Query, extensions=[ DjangoOptimizerExtension, ],) Query with filters:
query { fruits(category: "BERRY", colorName: "red") { name category color { name } }} Adding Mutations
Allow clients to create and update fruits:
import strawberryfrom strawberry_django import mutationsfrom .types import Fruitfrom . import models
@strawberry_django.input(models.Fruit)class FruitInput: name: auto category: auto color_id: auto
@strawberry_django.partial(models.Fruit)class FruitInputPartial(strawberry.relay.NodeInput): name: auto category: auto color_id: auto
@strawberry.typeclass Mutation: # Automatic CRUD mutations with Django error handling create_fruit: Fruit = mutations.create( FruitInput, handle_django_errors=True )
update_fruit: Fruit = mutations.update( FruitInputPartial, handle_django_errors=True )
delete_fruit: Fruit = mutations.delete( FruitInputPartial, # Need input type with id field handle_django_errors=True )
schema = strawberry.Schema( query=Query, mutation=Mutation, extensions=[ DjangoOptimizerExtension, ],) Create a fruit:
mutation { createFruit(data: { name: "Blueberry", category: "BERRY", colorId: "1" }) { ... on Fruit { id name category } ... on OperationInfo { messages { field message } } }} Adding Pagination
Limit the number of results for better performance:
from strawberry_django.pagination import OffsetPaginationInput
@strawberry.typeclass Query: @strawberry_django.field def fruits( self, pagination: Optional[OffsetPaginationInput] = None ) -> list[Fruit]: """Get fruits with pagination""" queryset = models.Fruit.objects.all()
if pagination: queryset = queryset[pagination.offset:pagination.offset + pagination.limit] else: queryset = queryset[:20] # Default limit
return queryset Query with pagination:
query { fruits(pagination: { offset: 0, limit: 10 }) { name category }} Adding Authentication
Protect your API with authentication:
from strawberry.permission import BasePermissionfrom strawberry.types import Info
class IsAuthenticated(BasePermission): message = "User is not authenticated"
def has_permission(self, source, info: Info, **kwargs) -> bool: return info.context.request.user.is_authenticated
@strawberry.typeclass Mutation: @strawberry.mutation(permission_classes=[IsAuthenticated]) def create_fruit(self, info: Info, name: str, category: str) -> Fruit: """Create a fruit (requires authentication)""" return models.Fruit.objects.create( name=name, category=category, created_by=info.context.request.user ) Computed Fields
Add fields that are computed rather than stored:
import strawberry_djangofrom strawberry import auto
@strawberry_django.type(models.Fruit)class Fruit: id: auto name: auto category: auto color: "Color"
@strawberry_django.field def display_name(self) -> str: """Computed field: formatted display name""" return f"{self.name} ({self.category})"
@strawberry_django.field def is_citrus(self) -> bool: """Computed field: check if fruit is citrus""" return self.category == models.FruitCategory.CITRUS Query computed fields:
query { fruits { name displayName isCitrus }} Optimizing Performance
The DjangoOptimizerExtension automatically prevents N+1 query problems:
# Without optimizer: 1 query for fruits + N queries for colors (N+1 problem)# With optimizer: 2 queries total (1 for fruits + 1 JOIN for colors)
@strawberry.typeclass Query: fruits: list[Fruit] = strawberry_django.field()
# Query that fetches related data efficientlyquery = """ query { fruits { name color { name # No N+1 problem thanks to optimizer! } } }""" See the Performance Guide for more optimization strategies.
Error Handling
Handle validation and database errors gracefully:
@strawberry.typeclass Mutation: create_fruit: Fruit = mutations.create( FruitInput, handle_django_errors=True # Automatically returns structured errors ) Error response format:
{ "data": { "createFruit": { "__typename": "OperationInfo", "messages": [ { "field": "name", "message": "This field is required", "kind": "VALIDATION" } ] } }} See the Error Handling Guide for comprehensive error management.
Complete Example
Here’s a complete example bringing everything together:
import strawberryfrom strawberry_django import mutationsfrom strawberry_django.optimizer import DjangoOptimizerExtensionfrom strawberry_django.pagination import OffsetPaginationInputfrom strawberry.permission import BasePermissionfrom strawberry.types import Infofrom typing import Optional
from .types import Fruit, Colorfrom . import models
class IsAuthenticated(BasePermission): message = "User is not authenticated"
def has_permission(self, source, info: Info, **kwargs) -> bool: return info.context.request.user.is_authenticated
@strawberry.typeclass Query: @strawberry_django.field def fruits( self, category: Optional[str] = None, pagination: Optional[OffsetPaginationInput] = None ) -> list[Fruit]: """Get fruits with optional filtering and pagination""" queryset = models.Fruit.objects.all()
if category: queryset = queryset.filter(category=category)
if pagination: queryset = queryset[pagination.offset:pagination.offset + pagination.limit] else: queryset = queryset[:20]
return queryset
@strawberry_django.field def fruit(self, id: strawberry.ID) -> Optional[Fruit]: """Get a single fruit by ID""" return models.Fruit.objects.filter(id=id).first()
@strawberry_django.field def colors(self) -> list[Color]: """Get all colors""" return models.Color.objects.all()
@strawberry.typeclass Mutation: # CRUD operations with automatic error handling create_fruit: Fruit = mutations.create( FruitInput, handle_django_errors=True )
update_fruit: Fruit = mutations.update( FruitInputPartial, handle_django_errors=True )
@strawberry.mutation(permission_classes=[IsAuthenticated]) def delete_fruit(self, info: Info, id: strawberry.ID) -> bool: """Delete a fruit (requires authentication)""" models.Fruit.objects.filter(id=id).delete() return True
schema = strawberry.Schema( query=Query, mutation=Mutation, extensions=[ DjangoOptimizerExtension, # Prevents N+1 queries ],) Example queries:
# Get all fruits with their colorsquery GetAllFruits { fruits { id name category displayName color { name } }}
# Get filtered and paginated fruitsquery GetBerries { fruits(category: "BERRY", pagination: { offset: 0, limit: 5 }) { name color { name } }}
# Create a new fruitmutation CreateFruit { createFruit(data: { name: "Raspberry", category: "BERRY", colorId: "1" }) { ... on Fruit { id name category } ... on OperationInfo { messages { field message kind } } }}
# Update a fruitmutation UpdateFruit { updateFruit(id: "1", data: { name: "Updated Strawberry" }) { ... on Fruit { id name } ... on OperationInfo { messages { field message } } }} Next Steps
Now that you have a working GraphQL API with common features, explore these guides to learn more:
Essential Guides
- Types - Define complex GraphQL types from Django models
- Fields - Customize field behavior and add computed fields
- Mutations - Create, update, and delete operations
- Filters - Advanced filtering capabilities
- Pagination - Efficient data pagination strategies
Performance & Optimization
- Query Optimizer - Prevent N+1 queries automatically
- Performance - Database optimization and caching
- DataLoaders - Custom data loading patterns
Security & Validation
- Permissions - Protect your API with authorization
- Validation - Input validation and error handling
- Error Handling - Comprehensive error management
Advanced Topics
- Relay - Relay-style pagination and connections
- Subscriptions - Real-time updates with WebSockets
- Model Properties - Optimize computed properties
- Nested Mutations - Handle complex relationships
- Unit Testing - Test your GraphQL API
Help & Resources
- FAQ - Frequently asked questions
- Troubleshooting - Common issues and solutions
- Example App - Complete working example