Strawberry GraphQL Django
repository·main·Indexed 19 days ago
https://github.com/strawberry-graphql/strawberry-djangoA Django extension for Strawberry GraphQL that provides tools to build APIs by automatically generating types, queries, mutations, and resolvers from Django models. It includes features for data management such as filtering, ordering, and Relay-compliant pagination, as well as performance tools like a Query Optimizer and DataLoaders to solve the N+1 problem. The integration supports Federation, Django Channels for subscriptions, and field-level security via permission extensions.
What's inside strawberry-graphql-django
- To begin using Strawberry Django, start with the Quick Start guide to set up a basic integration. The project provides core abstractions for mapping Django models to GraphQL types, handling queries, mutations, and subscriptions, and managing data through views.
Explore community-maintained Strawberry Django projects
mainThe following community projects provide extended functionality for Strawberry Django integrations:
- strawberry-django-auth: An authentication system for Django using Strawberry.
- strawberry-django-extras: Provides JWT authentication, input validation, permissions, mutation hooks, and support for deeply nested CUD (Create, Update, Delete) mutations.
Features of strawberry-graphql-django
mainThe integration provides several automated features for building GraphQL APIs:
- Automatic Type Generation: Generate GraphQL types from Django models with full type safety.
- Advanced Filtering: Powerful filtering system with lookups (contains, exact, in, etc.).
- Pagination: Built-in offset and cursor-based (Relay) pagination.
- Ordering: Sort results by any field with automatic ordering support.
- Authentication & Permissions: Django auth integration with a flexible permission system.
- CRUD Mutations: Auto-generated create, update, and delete mutations with validation.
- Query Optimizer: Automatic
select_relatedandprefetch_relatedto prevent N+1 queries viaDjangoOptimizerExtension. - Django Integration: Works with Django views (sync and async), forms, and validation.
- Debug Toolbar: GraphiQL integration with Django Debug Toolbar for query inspection.
Prevent N+1 queries with DjangoOptimizerExtension
mainThe
DjangoOptimizerExtensionis a schema extension that automatically optimizes database queries to prevent the N+1 problem. When fetching related data (e.g., fetching a list of fruits and their associated colors), the extension uses joins or optimized fetching to reduce the total number of database hits.from strawberry_django.optimizer import DjangoOptimizerExtension schema = strawberry.Schema( query=Query, mutation=Mutation, extensions=[ DjangoOptimizerExtension, ], )Advanced Features and Performance
mainFor complex applications, Strawberry Django offers advanced tools:
- Query Optimizer: Reducing database hits.
- DataLoaders: Solving the N+1 problem by batching requests.
- Nested Mutations: Performing complex updates on related models.
- Resolvers: Customizing how specific fields are resolved.
Use DataLoaders for complex data fetching
mainWhen the
DjangoOptimizerExtensionis insufficient—such as when fetching data from external APIs, performing complex custom aggregations, or handling non-standard relationship patterns—use Strawberry'sDataLoader. DataLoaders batch and cache requests to prevent redundant work.from strawberry.dataloader import DataLoader from typing import List # 1. Define the batch loading function async def load_authors(keys: List[int]) -> List[Author]: """Batch load authors by ID""" authors = Author.objects.filter(id__in=keys) author_map = {author.id: author for author in authors} return [author_map.get(key) for key in keys] # 2. Add the loader to your context def get_context(): return {"author_loader": DataLoader(load_fn=load_authors)} # 3. Use the loader in a resolver @strawberry.field async def author(self, info) -> Author: loader = info.context["author_loader"] return await loader.load(self.author_id)Security, Validation, and Testing
mainEnsure your GraphQL API is secure and robust using:
- Permissions & Authentication: Controlling access to fields and mutations.
- Validation: Ensuring incoming data adheres to business rules.
- Error Handling: Managing how GraphQL errors are returned to clients.
- Unit Testing: Testing your schema and resolvers within a Django environment.
Perform partial updates using Maybe types
mainWhen using
@strawberry_django.partialfor updates, allautofields become optional. To distinguish between a field being omitted (no change) and a field being explicitly set to null, use theMaybetype pattern:Maybe[T]: The field is either absent (None) or has a value (Some(value)). Use this for required fields that you want to allow omitting during an update.Maybe[T | None]: The field is absent (None), has a value (Some(value)), or is explicitly null (Some(None)). Use this for nullable fields where you need to support setting them to null.
Implementation Pattern: When resolving the mutation, check if the
Maybewrapper isnot Nonebefore applying the value.from strawberry import Maybe @strawberry_django.input(models.Fruit) class FruitUpdateInput: id: strawberry.relay.GlobalID # name is required, null not allowed name: Maybe[str] # color is optional, can be explicitly set to null color: Maybe[str | None] @strawberry.type class Mutation: @strawberry_django.mutation def update_fruit(self, info, input: FruitUpdateInput) -> Fruit: fruit = input.id.resolve_node_sync(info) if input.name is not None: fruit.name = input.name.value if input.color is not None: # input.color.value is either a string or None fruit.color = input.color.value fruit.save() return cast(Fruit, fruit)Use DjangoCursorConnection for efficient pagination
mainFor large datasets, use
DjangoCursorConnectioninstead of the defaultListConnection.Key Differences:
- Performance:
ListConnectionuses SQLOFFSET(slicing), which slows down as page numbers increase.DjangoCursorConnectionuses range queries (e.g.,Q(field__gte=...)), which is more efficient when used with database indexes. - Use Case: Best for infinitely scrollable lists. It does not support jumping to specific pages.
- Ordering Requirement: Requires a strictly ordered
QuerySet. If the ordering is not unique (e.g.,order_by('date')),DjangoCursorConnectionwill automatically append the primary key to the order to ensure stability. - Compatibility Note: If the order is configurable by the user (e.g., via
@strawberry_django.order), cursors will not be compatible between different sort orders.
@strawberry.type class Query: # Uses range-based pagination via Django QuerySets fruit: DjangoCursorConnection[FruitType] = strawberry_django.connection() @strawberry_django.connection(DjangoCursorConnection[FruitType]) def fruit_with_custom_resolver(self) -> list[Fruit]: return Fruit.objects.all()- Performance:
Optimize queries: Query Optimizer vs DataLoaders
mainChoosing between the Query Optimizer and DataLoaders depends on your use case:
Use the Query Optimizer (Recommended)
Use
DjangoOptimizerExtensionfor most standard Django ORM scenarios. It provides automatic optimization, handles most N+1 scenarios, and requires less maintenance.Use DataLoaders
Use DataLoaders when you need:
- Custom batching logic
- Fetching data from external APIs
- Fine-grained caching control
- Scenarios where the optimizer cannot handle the specific logic
from strawberry_django.optimizer import DjangoOptimizerExtension schema = strawberry.Schema( query=Query, extensions=[DjangoOptimizerExtension], )Optimizing polymorphic queries
mainThe Query Optimizer supports fields that return an interface (polymorphic queries). It automatically handles optimizing subtypes of the interface for both top-level queries and model relations.
Supported Polymorphic Approaches
- Django Polymorphic: Works out of the box if you use the
django-polymorphiclibrary. - django-model-utils InheritanceManager: Supported automatically. The optimizer calls
select_subclasses, passing in subtypes present in your schema. - Custom Polymorphic Solution: If your models are not polymorphic, you can implement a custom solution by defining
resolve_typein your GraphQL interface type to map instances to their correct GraphQL types.
Important Constraints
- Filtering: The optimizer does not filter your QuerySet. Django will return all instances of the model. You must ensure every model subclass has a corresponding GraphQL type, or implement a
get_querysetmethod on your interface type to filter out unwanted subtypes. Failure to do this may result in the error:Abstract type 'ProjectType' must resolve to an Object type at runtime for field 'Query.projects'. - Custom Solutions & Relations: When using a custom
resolve_typeapproach, you must addget_querysetto your interface type to force the optimizer to useprefetch_related; otherwise, optimization will not work for relation fields. - InheritanceManager & Base Managers: If you have polymorphic relations, the manager used to look up the related model must be an
InheritanceManager. Since Strawberry Django uses the model's base manager by default (notobjects), you should either change your base manager to anInheritanceManageror configure the extension withprefetch_custom_queryset=True.
@strawberry_django.interface(models.Project) class ProjectType: topic: strawberry.auto @classmethod def resolve_type(cls, value, info, parent_type) -> str: if value.artist: return "ArtProjectType" if value.supervisor: return "ResearchProjectType" raise TypeError() @classmethod def get_queryset(cls, qs, info): return qs @strawberry_django.type(models.ResearchProject) class ResearchProjectType(ProjectType): supervisor: strawberry.auto @strawberry_django.type(models.ArtProject) class ArtProjectType(ProjectType): artist: strawberry.auto- Django Polymorphic: Works out of the box if you use the
Best practices for Strawberry Django queries
mainTo ensure high performance and maintainability, follow these best practices:
- Enable the Query Optimizer: Always add
DjangoOptimizerExtension()to yourstrawberry.Schemato prevent N+1 queries. - Paginate list queries: Prevent performance degradation by avoiding returning unbounded lists.
- Use filters and ordering: Empower clients to request exactly the data they need.
- Add appropriate indexes: Ensure database fields used in filters and ordering are indexed.
- Use custom resolvers sparingly: Default resolvers are highly optimized; only override them when necessary.
- Leverage annotations: Perform calculations at the database level rather than in Python.
- Test query performance: Monitor SQL execution during development.
- Enable the Query Optimizer: Always add