- WebSocket-based GraphQL server implemented on the Django Channels.
- Graphene-like subscriptions.
- Subscription groups based on the Django Channels groups.
- Parallel execution of requests.
Installation:
pip install django-channels-graphql-ws
The channels_graphql_ws
module provides two classes: Subscription
and GraphqlWsConsumer
. The Subscription
class itself is a "creative"
copy of Mutation
class from the Graphene
(graphene/types/mutation.py
). The GraphqlWsConsumer
is a Channels
WebSocket consumer which maintains WebSocket connection with the client.
Create GraphQL schema, e.g. using Graphene (note that subscription definition syntax is close to the Graphene mutation definition):
import channels_graphql_ws
import graphene
class MySubscription(channels_graphql_ws.Subscription):
"""Sample GraphQL subscription."""
# Subscription payload.
event = graphene.String()
class Arguments:
"""That is how subscription arguments are defined."""
arg1 = graphene.String()
arg2 = graphene.String()
def subscribe(self, info, arg1, arg2):
"""Called when user subscribes."""
# Return the list of subscription group names.
return ['group42']
def publish(self, info, arg1, arg2):
"""Called to notify the client."""
# Here `self` contains the `payload` from the `broadcast()`
# invocation (see below).
return MySubscription(event='Something has happened!')
class Query(graphene.ObjectType):
"""Root GraphQL query."""
# Check Graphene docs to see how to define queries.
pass
class Mutation(graphene.ObjectType):
"""Root GraphQL mutation."""
# Check Graphene docs to see how to define mutations.
pass
class Subscription(graphene.ObjectType):
"""Root GraphQL subscription."""
my_subscription = MySubscription.Field()
graphql_schema = graphene.Schema(
query=Query,
mutation=Mutation,
subscription=Subscription,
)
Make your own WebSocket consumer subclass and set the schema it serves:
class MyGraphqlWsConsumer(channels_graphql_ws.GraphqlWsConsumer):
"""Channels WebSocket consumer which provides GraphQL API."""
schema = graphql_schema
Setup Django Channels routing:
application = channels.routing.ProtocolTypeRouter({
'websocket': channels.routing.URLRouter([
django.urls.path('graphql/', MyGraphqlWsConsumer),
])
})
Notify clients when some event happens:
MySubscription.broadcast(
# Subscription group to notify clients in.
group='group42',
# Dict delivered to the `publish` method as the `self` argument.
payload={},
)
For details check the source code
which is thoroughly commented. (The docstrings of the Subscription
class in especially useful.)
Since the WebSocket handling is based on the Django Channels and subscriptions are implemented in the Graphene-like style it is recommended to have a look the documentation of these great projects:
The implemented WebSocket-based protocol was taken from the library subscription-transport-ws which is used by the Apollo GraphQL. Check the protocol description for details.
- Different requests from different WebSocket client are processed asynchronously.
- By default different requests (WebSocket messages) from a single client are processed concurrently in different worker threads. So there is no guarantee that requests will be processed in the same the client sent these requests. Actually, with HTTP we have this behavior for years.
- It is possible to serialize message processing by setting
strict_ordering
toTrue
. But note, this disables parallel requests execution - in other words, the server will not start processing another request from the client before it finishes the current one. See comments in the classGraphqlWsConsumer
.
There is a Tomáš Ehrlich GitHubGist GraphQL Subscription with django-channels which this implementation was initially based on.
There is a promising GraphQL WS library by the Graphene authors. In particular this pull request gives a hope that there will be native Graphene implementation of the WebSocket transport with subscriptions one day.
Just a reminder of how to setup an environment for the development:
> python3 -m venv .venv
> direnv allow
> pip install poetry
> poetry install
> pre-commit install
> pytest
Use:
This project is developed and maintained by DATADVANCE LLC. Please submit an issue if you have any questions or want to suggest an improvement.
This work is supported by the Russian Foundation for Basic Research (project No. 15-29-07043).