GraphQL is a query language for your API. A top benefit is that it gives the client complete power over the query responses. This means that you can get or modify your query to only return the data you need. It gives frontend developers more flexibility. GraphQL was invented by Dan Schafer and Nick Schrock of Facebook in 2012 and open-sourced in 2015. To use graphQL in Django, you need to install the package called graphene_django.
Direct steps to setting up a GraphQL project
Create a Django app
Create your models the usual way you would create typical models
from django.db import models
class Actor(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Meta:
ordering = ('name',)
class Movie(models.Model):
title = models.CharField(max_length=100)
actors = models.ManyToManyField(Actor)
year = models.IntegerField()
def __str__(self):
return self.title
class Meta:
ordering = ('title',)
install graphene-django using pip install graphene-django
Add graphene_django to your installed apps
In your settings.py file, specify the schema file that acts as a single entry point for all the queries and mutation
GRAPHENE = {
'SCHEMA': 'django_graphql_movies.schema.schema'
}
- In your project folder, create a schema.py file, it should include two classes, Query and Mutation, and these should extend the Query and Mutation classes you will create in your app. Note that this file is different from the schema.py file to be created inside your app.
import graphene
from .movies.schema import Query, Mutation
class Query(Query, graphene.ObjectType):
pass
class Mutation(Mutation, graphene.ObjectType):
pass
schema = graphene.Schema(query=Query, mutation=Mutation)
GraphQL only exposes a single endpoint that allows you to send "POST" requests. Basically, every graphQL request is a "POST" request.
Queries
These allow you to fetch data or get data from the server. This is like a rest API's get request.
Mutations
These are used to write or modify data in the database, i.e it would be like a rest API's post or patch request.
- Add the 'graphql' URL to the root URL.
from django.contrib import admin
from django.urls import path
from graphene_django.views import GraphQLView
from django_graphql_movies.schema import schema
urlpatterns = [
path('admin/', admin.site.urls),
path('graphql/', GraphQLView.as_view(graphiql=True)),
]
- In your app, create a schema.py file and define the types for your models. Types are basically a declaration of the parameters your queries or mutation should give or receive respectively. These are the query types of our simple movie app.
# Create a GraphQL type for the actor model
class ActorType(DjangoObjectType):
class Meta:
model = Actor
# Create a GraphQL type for the movie model
class MovieType(DjangoObjectType):
class Meta:
model = Movie
Writing a Query
- In your app's schema.py file, create a Query class, define/register your different queries using the graphene type you want as a response. Each query should extend a type that would represent its structure, and can also extend other values.
# Create a Query type
class Query(ObjectType):
actor = graphene.Field(ActorType, id=graphene.Int())
movie = graphene.Field(MovieType, id=graphene.Int())
actors = graphene.List(ActorType)
movies = graphene.List(MovieType)
def resolve_actor(self, info, **kwargs):
id = kwargs.get('id')
if id:
return Actor.objects.get(pk=id)
return None
def resolve_movie(self, info, **kwargs):
id = kwargs.get('id')
if id:
return Movie.objects.get(pk=id)
return None
def resolve_actors(self, info, **kwargs):
return Actor.objects.all()
def resolve_movies(self, info, **kwargs):
return Movie.objects.all()
- Each query class takes as many fields as you want to represent, i.e you can pass arguments to queries, these arguments include filter and search parameters. Each field needs to be resolved with a matching method. The function must begin with the "resolve" keyword. Here is a look:
def resolve_actor(self, info, **kwargs):
id = kwargs.get('id')
if id is not None:
return Actor.objects.get(pk=id)
return None
def resolve_movie(self, info, **kwargs):
id = kwargs.get('id')
if id is not None:
return Movie.objects.get(pk=id)
return None
def resolve_actors(self, info, **kwargs):
return Actor.objects.all()
def resolve_movies(self, info, **kwargs):
return Movie.objects.all()
To note, in graphQL snake_case_namings are converted to camelCase names.
Writing a Mutation
- Create the input type, this basically declares the parameters your mutation needs to make a request to your API, i.e the data structure of your mutation requests.
class ActorInput(graphene.InputObjectType):
id = graphene.ID()
name = graphene.String()
class MovieInput(graphene.InputObjectType):
id = graphene.ID()
title = graphene.String()
actors = graphene.List(ActorInput)
year = graphene.Int()
- For each mutation, create a mutation class where the actual work is done. This class should have a "mutate" function where the functionality of the mutation is written, things such as storing into the database, etc.
# Create mutations for actors
class CreateActor(graphene.Mutation):
class Arguments:
input = ActorInput(required=True)
ok = graphene.Boolean()
actor = graphene.Field(ActorType)
@staticmethod
def mutate(root, info, input=None):
ok = True
actor_instance = Actor(name=input.name)
actor_instance.save()
return CreateActor(ok=ok, actor=actor_instance)
class UpdateActor(graphene.Mutation):
class Arguments:
id = graphene.Int(required=True)
input = ActorInput(required=True)
ok = graphene.Boolean()
actor = graphene.Field(ActorType)
@staticmethod
def mutate(root, info, id, input=None):
ok = False
actor_instance = Actor.objects.get(pk=id)
if actor_instance:
ok = True
actor_instance.name = input.name
actor_instance.save()
return UpdateActor(ok=ok, actor=actor_instance)
return UpdateActor(ok=ok, actor=None)
# Create mutations for movies
class CreateMovie(graphene.Mutation):
class Arguments:
input = MovieInput(required=True)
ok = graphene.Boolean()
movie = graphene.Field(MovieType)
@staticmethod
def mutate(root, info, input=None):
ok = True
actors = []
for actor_input in input.actors:
actor = Actor.objects.get(pk=actor_input.id)
if actor is None:
return CreateMovie(ok=False, movie=None)
actors.append(actor)
movie_instance = Movie(
title=input.title,
year=input.year
)
movie_instance.save()
movie_instance.actors.set(actors)
return CreateMovie(ok=ok, movie=movie_instance)
class UpdateMovie(graphene.Mutation):
class Arguments:
id = graphene.Int(required=True)
input = MovieInput(required=True)
ok = graphene.Boolean()
movie = graphene.Field(MovieType)
@staticmethod
def mutate(root, info, id, input=None):
ok = False
movie_instance = Movie.objects.get(pk=id)
if movie_instance:
ok = True
actors = []
for actor_input in input.actors:
actor = Actor.objects.get(pk=actor_input.id)
if actor is None:
return UpdateMovie(ok=False, movie=None)
actors.append(actor)
movie_instance.title=input.title
movie_instance.year=input.year
movie_instance.save()
movie_instance.actors.set(actors)
return UpdateMovie(ok=ok, movie=movie_instance)
return UpdateMovie(ok=ok, movie=None)
- Register all mutations under the general Mutation class, and register and set your schema on graphene.Schema
class Mutation(graphene.ObjectType):
create_actor = CreateActor.Field()
update_actor = UpdateActor.Field()
create_movie = CreateMovie.Field()
update_movie = UpdateMovie.Field()
schema = graphene.Schema(query=Query, mutation=Mutation)
And your graphql application is good to go.
Testing your app
Like I mentioned earlier, there is only one entry point for a graphql app, "/graphql/. Because my app is not host, I will be using the localhost test URL.
http://127.0.0.1:8000/graphql/
To test your API, you can run your URL on the normal browser, or use API client apps such as Postman and Altair.
Make a Mutation: Create an Actor
Make a Query: Get all Actors
Make a Query: Get Single Actor by ID
Again, The dexterity behind GraphQL that allows clients the ability to craft and demand only specific data from endpoints will always be a plus over REST. So Django x Graphql, Yay or Nay? I look forward to your responses, Thank You.