Graphene vs Strawberry:

Which is better for providing a GraphQL API?

by

Anton Fomin

Team Lead, Expert Full-Stack Developer

by

Anton Fomin

Team Lead, Expert Full-Stack Developer

by

Anton Fomin

Team Lead, Expert Full-Stack Developer

Lesezeit

6

Minuten

veröffentlicht

09.12.2022

teilen

email icon
x icon
facebook icon
copy icon

In M&P we have few projects using GraphQL APIs. And since most of the backends are created with Python and Django, we used the most starred GraphQL library for Python, which is Graphene. It has a good enough integration with Django, and we were pleased with the setup, until recently, we found that a lot of libraries around it are not maintained that well anymore, and it blocked us from updates.

Also, we had few issues with DX, so we started looking for an alternative, then we identified cases where DX with Graphene was not that great and checked how does it work with Strawberry.

1. Authentication

Graphene

There is no built-in mutations for user registration and authentication in Graphene. So, when we were looking for an existing solution, we stumbled across this library.

GitHub — PedroBern/django-graphql-auth: Django registration and authentication with GraphQL.Django registration and authentication with GraphQL. Abstract all the basic logic of handling user accounts out of your…github.com

It provides quite a lot: JWT-based authentication, mutations to register a user, obtain JWT token, refresh token, recover password, activate account. All with email templates. It is well documented as well, so we used it in our projects. Unfortunately, it is not maintained anymore and blocks updates of other dependencies.

Strawberry

So, we checked whether there are existing solutions for Strawberry.
There is no library which provides all the same functionality as django-graphql-auth , but there is a much simpler solution in the Django integration library.

GitHub — strawberry-graphql/strawberry-graphql-django: Strawberry GraphQL Django extensionThis package provides powerful tools to generate GraphQL types, queries, mutations and resolvers from Django models…github.com

It provides mutations for the user registration, login, and logout. The mutations themselves are not really flexible, but quite small and easy to inherit/extend or rewrite if needed.

The approach to authentication is much simpler than with Graphene’s graphql-auth library. It reuses default Django authentication, and it’s much easier to integrate and work with. Also, web sockets and file downloads get authentication out of the box. So in most of our cases it works better than JWT-based authentication.

See https://strawberry-graphql.github.io/strawberry-graphql-django/references/authentication/ for more information.

Current Standing:
Strawberry: ⭐️
Graphene:

2. CUD operations on Django models

Graphene

There are few ways to do CUD (create, update, delete) operations on Django models in Graphene without much boilerplate:

Built in into Django integration:

  • DjangoModelFormMutation — which does not work if you have relay (and you are forced to have it if you use Graphene Django filters integration)

  • Django REST Framework SerializerMutation — the same problem as above, plus you need to add Django rest framework as a dependency even if you only need this kind of mutation.

Third party:

There is graphene-django-plus library, which does this job well, but unfortunately, it is not maintained anymore.

Strawberry

CUD operations on Django models provided by the same library which provides authentication.

GitHub — strawberry-graphql/strawberry-graphql-django: Strawberry GraphQL Django extensionThis package provides powerful tools to generate GraphQL types, queries, mutations and resolvers from Django models…github.com

There are few drawbacks, though:

  1. The crud model provided by the library is not very flexible. For example, the name of the input variable cannot be changed.

  2. Update mutation is implemented in a way that it allows you to update multiple rows at once. That is not needed in most of our cases and also make it quite easy to create update mutation which can update more than was intended.

We tried to find whether it can be overridden and found out that it is actually effortless to copy or extend necessary mutations.

The CUD mutations implementation can be found here https://github.com/strawberry-graphql/strawberry-graphql-django/blob/main/strawberry_django/mutations/fields.py

And we managed to create our own update mutation which accepts a mandatory ID attribute and data to perform update on a single row.

class DjangoUpdateMutation(    DjangoMutationBase,    StrawberryDjangoFieldBase,    StrawberryField,):    @transaction.atomic    def resolver(self, info, source, id, input, **kwargs):        queryset = self.django_model.objects.filter(pk=id)        input_data = get_input_data(self.input_type, input)        queryset.update(**input_data)        update_m2m(queryset, input)        return queryset.first()    @property    def arguments(self):        return [            StrawberryArgument(                python_name="id",                graphql_name="id",                type_annotation=StrawberryAnnotation(strawberry.ID),            )        ] + super().arguments

Current Standing:
Strawberry: ⭐️⭐️
Graphene:️

3. Subscriptions

On the frontend, there are two implementations of GraphQL over WebSocket subscriptions-transport-ws (outdated) and graphql-ws (new and shiny one).

We have projects with Graphene having subscriptions. Because of the unmaintained dependencies, we had problems setting it up compatible with graphql-ws .

Strawberry subscriptions worked out of the box with the new protocol.

In both cases, using a subscription over Web Sockets together with queries and mutations over HTTP may require you to implement custom contexts or handle authentication slightly different.

Current Standing:
Strawberry: ⭐️⭐️⭐️
Graphene:️

4. Data loaders

Before v3, Graphene used promise-based Data loaders. After v3 was introduced, the documentation was broken for a long time, and it was not clear how to make them work. These days it seems it is fixed, and you can just use aiodataloader package.

In Strawberry, they have got their own async data loaders, which just work.

Current Standing:
This is a draw, so no changes in the standing here

5. Cyclic imports handling

With GraphQL it is really easy to introduce cyclic dependencies. For example, in a blog application you may have User -> Blog -> Comment -> User chain.

Both libraries provide solutions to work with such dependencies.

In Graphene, you may use strings or lambdas. For example:

user = graphene.NonNull("User")#oruser = graphene.NonNull(lambda: User)

But in case with lambda you may still have circular imports problem.

Strawberry make usetyping.Annotated and strawberry.lazy to solve the circular dependencies problem.

Lazy Types | 🍓 Strawberry GraphQL

Since with this approach imports can be guarded with if TYPE_CHECKING this also solves circular imports problem.

The only problem I found is that if you import from __future__ import annotations it breaks these lazy types and the error message is very cryptic and impossible to understand.

Current Standing:
Strawberry: ⭐️⭐️⭐
Graphene: ️️

6. Unit testing

Both libraries provide equal functionality, which allows you to execute any query on a schema.

We were particularly interested whether Strawberry can provide anything to serialize a specific type, but there was nothing built-in, but there are ways to execute requests with mock context, root values. So, we end up with the following helper:

async def graphql_serialize(graphql_type: Type, query: str, root: Any) -> dict[str, Any]:    @strawberry.type    class Query:        obj: graphql_type    schema = Schema(query=Query)    result = await schema.execute(f"query {{ obj {{ {query} }}}}", root_value=Query(obj=root), context_value=Context(MagicMock(), MagicMock()))    assert not result.errors    return result.data["obj"]

That can allow testing serialization of mock models not yet stored to the database:

async def test_user_serialization():    result = await graphql_serialize(User, "id firstName", models.User(id=1, first_name="Luke"))    assert result == {"id": "1", "firstName": "Luke"}

It should be possible to do the same with Graphene.

Current Standing:
This is a draw, so no changes in the standing here

7. GitHub insights

Just looking at GitHub insights, strawberry seems better maintained

Press enter or click to view image in full size

Current Standing:
Strawberry: ⭐️️️⭐️⭐️⭐⭐️️️
Graphene: ️

Conclusion: Strawberry is the Winner! 🍓

Both libraries provide more or less the same, but developer experience was much better with Strawberry. Most of the things just worked as expected and there were no problems with dependencies and libraries incompatibilities.

The integration with Django is not perfect, but where it is needed it is possible to reuse something and add overrides.

by

Anton Fomin

Team Lead, Expert Full-Stack Developer

by

Anton Fomin

Team Lead, Expert Full-Stack Developer

by

Anton Fomin

Team Lead, Expert Full-Stack Developer

by

Anton Fomin

Team Lead, Expert Full-Stack Developer

veröffentlicht

09.12.2022

teilen

email icon
x icon
facebook icon
copy icon

Aktuelle Artikel

Aktuelle Artikel

Aktuelle Artikel

Bereit

durchzustarten?

Bereit

durchzustarten?

Bereit

durchzustarten?