Strawberry GraphQL Django

repository·main·Indexed 19 days ago

https://github.com/strawberry-graphql/strawberry-django

A 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.

Tokens
63.7K
Snippets
203
Records
238
Agent score
66%

What's inside strawberry-graphql-django

  1. Explore community-maintained Strawberry Django projects

    main

    The 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.
  2. Features of strawberry-graphql-django

    main

    The 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_related and prefetch_related to prevent N+1 queries via DjangoOptimizerExtension.
    • Django Integration: Works with Django views (sync and async), forms, and validation.
    • Debug Toolbar: GraphiQL integration with Django Debug Toolbar for query inspection.
  3. Prevent N+1 queries with DjangoOptimizerExtension

    main

    The DjangoOptimizerExtension is 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,
        ],
    )
  4. Advanced Features and Performance

    main

    For 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.
  5. Use DataLoaders for complex data fetching

    main

    When the DjangoOptimizerExtension is insufficient—such as when fetching data from external APIs, performing complex custom aggregations, or handling non-standard relationship patterns—use Strawberry's DataLoader. 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)
  6. Security, Validation, and Testing

    main

    Ensure 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.
  7. Perform partial updates using Maybe types

    main

    When using @strawberry_django.partial for updates, all auto fields become optional. To distinguish between a field being omitted (no change) and a field being explicitly set to null, use the Maybe type pattern:

    1. 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.
    2. 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 Maybe wrapper is not None before 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)
  8. Use DjangoCursorConnection for efficient pagination

    main

    For large datasets, use DjangoCursorConnection instead of the default ListConnection.

    Key Differences:

    • Performance: ListConnection uses SQL OFFSET (slicing), which slows down as page numbers increase. DjangoCursorConnection uses 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')), DjangoCursorConnection will 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()
  9. Optimize queries: Query Optimizer vs DataLoaders

    main

    Choosing between the Query Optimizer and DataLoaders depends on your use case:

    Use DjangoOptimizerExtension for 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],
    )
  10. Optimizing polymorphic queries

    main

    The 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

    1. Django Polymorphic: Works out of the box if you use the django-polymorphic library.
    2. django-model-utils InheritanceManager: Supported automatically. The optimizer calls select_subclasses, passing in subtypes present in your schema.
    3. Custom Polymorphic Solution: If your models are not polymorphic, you can implement a custom solution by defining resolve_type in 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_queryset method 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_type approach, you must add get_queryset to your interface type to force the optimizer to use prefetch_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 (not objects), you should either change your base manager to an InheritanceManager or configure the extension with prefetch_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
  11. Best practices for Strawberry Django queries

    main

    To ensure high performance and maintainability, follow these best practices:

    1. Enable the Query Optimizer: Always add DjangoOptimizerExtension() to your strawberry.Schema to prevent N+1 queries.
    2. Paginate list queries: Prevent performance degradation by avoiding returning unbounded lists.
    3. Use filters and ordering: Empower clients to request exactly the data they need.
    4. Add appropriate indexes: Ensure database fields used in filters and ordering are indexed.
    5. Use custom resolvers sparingly: Default resolvers are highly optimized; only override them when necessary.
    6. Leverage annotations: Perform calculations at the database level rather than in Python.
    7. Test query performance: Monitor SQL execution during development.