Enhancing GraphQL Capabilities in Django: The New Annotation Feature in strawberry-django

Flávio Juvenal
August 14, 2024

At Vinta, we've been experimenting with the strawberry-django library, the state-of-the-art GraphQL implementation for Django. This post explores our recent contribution to strawberry-django: the support for ORM annotations as GraphQL fields, which enables complex queries with minimal boilerplate code.

The New Annotation Feature in strawberry-django

strawberry-django brings the simplicity and efficiency of GraphQL to Django applications. GraphQL allows developers to request exactly the data they need, reducing the overhead typically associated with REST APIs.

The missing killer-feature: ORM Annotations

When evaluating the use of strawberry-django for Vinta's existing and new projects, we were impressed by the built-in Query Optimizer, and how it could automatically call select_related(), prefetch_related(), and only() on querysets based on each GraphQL query. Having this sort of auto-optimization means the GraphQL query never overfetches, nor underfetches data from Django's DB.

Additionally, strawberry-django fetches data efficiently, using necessary JOINs and additional queries while avoiding unneeded N+1 queries, a common hurdle for ORMs and related libraries. However, strawberry-django's Query Optimizer missed one major feature from Django's ORM that Vinta uses in all our projects: Annotations.

Django ORM Annotations let developers declare new computed fields at query-time based on calculations over existing data and relationships. Sums, averages and even nested lists can be easily added to existing Django models through query annotations. The ease of enhancing queries with computed fields, along with the sheer variety of supported query expressions are a killer-feature of the Django ORM, when compared to competing Python ORMs.

The trouble is, strawberry-django didn't support ORM annotations yet!

How we contributed to strawberry-django

As open-source maintainers and contributors, our first thought was: let's then add the annotation feature ourselves!

First, we analyzed the Strawberry Django codebase to ensure the ORM annotation feature wasn't really available because it could be implemented already, just not documented. The codebase is well tested, and code quality is great, so we easily identified the Query Optimizer was responsible for building the querysets according to the current GraphQL query, and it became clear to us, it didn't support annotations.

Strawberry and its related libraries like Strawberry Django have a great open-source community with active and approachable maintainers alongside a thriving Discord community. We reached out to one of the Strawberry Django maintainers, Thiago Bellini, to validate the lack of support of ORM Annotations, and discuss the solution strategy. With Thiago's help, we quickly implemented, tested, and documented a change to the Query Optimizer for supporting annotations with a friendly syntax, similar to what the library already offered for concrete fields.

After our Pull Request was merged, strawberry-django supports annotated fields in GraphQL queries seamlessly. By incorporating ORM-level annotations directly into GraphQL queries, we reduce the need for post-processing data on the client side. This simplifies client-side code and ensures efficient data retrieval, which was critical for Vinta to introduce the library in our projects without any downsides.

Practical Example: Using the Annotation Feature

Here's a practical example: Suppose we have an e-commerce app where we need to calculate total sales for each product. With the new annotation feature in strawberry-django, we can define this calculation directly in our GraphQL query schema:


from decimal import Decimal
from django.db.models import Sum
from strawberry_django.optimizer import DjangoOptimizerExtension

@strawberry.type
class ProductType:
    name: auto
    total_sales: Decimal = strawberry_django.field(annotate=Sum('orders__amount'))

@strawberry.type
class Query:
    product_types: List[ProductType] = strawberry_django.field()

schema = strawberry.Schema(
    Query,
    extensions=[
        # other extensions...
        DjangoOptimizerExtension,
    ]
)

This GraphQL example shows how we can use Django ORM annotations within our GraphQL schema to calculate and expose total sales for products, optimizing both development time and query performance.

Conclusion

Strawberry GraphQL offers a modern, type-safe, and developer-friendly way to implement GraphQL APIs in Python. Its design principles and community support make it a valuable tool for developers looking to build efficient and maintainable GraphQL services.

Our contribution to strawberry-django reflects our commitment to open source. We invite you to explore this new feature, integrate it into your projects, and share your feedback with us. 

Special thanks to Thiago Bellini for the timely support on assisting with the annotation feature solution and review!