Django, GraphQL & React
import BlogPostImage from “~components/BlogPostImage.astro”;
Hello! Welcome back after a little break - I recently started working on a project that uses GraphQL. Thant’s why I thought that it will be the best to show you how to build a simple application using these tools. Let’s get started!
First, comes this idea - what application can I create so I will be able to use Django, GraphQL, React & Relay?
After few minutes/hours, I decided to create simple film database. In my Django application, I will be keeping records of actors & films. GraphQL will fetch them but also will get data from external source. React will consume GraphQL response using Relay.
For better understanding I created this diagram:
As you can see light blue color represents frontend part of a whole application. Yellow is GraphQL layer - I like to think about it in terms of a gate to API. API word is combined with Django Application that uses PostgreSQL database and external film API. They are in green color.
I will use a library called graphene-django. It will help a lot and allow me to get the job done instead of writing boilerplate code.
I decided I will have 3 django applications: actors, films and movies database. The first two should be self-explanatory, the last one is simple integration with third-party api - The Movie Data Base.
Let’s start from actors application - it will have actor model:
class Actor(models.Model):
RATING_CHOICES = (
(1, 1),
(2, 2),
(3, 3),
(4, 4),
(5, 5)
)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
age = models.PositiveSmallIntegerField()
rating = models.PositiveSmallIntegerField(choices=RATING_CHOICES)
def __str__(self):
return f'Actor: {self.first_name} {self.last_name}'
Here is the most important part of the design - schema:
import graphene
from graphene_django.types import DjangoObjectType
from .models import Actor
class ActorType(DjangoObjectType):
class Meta:
model = Actor
class Query(graphene.AbstractType):
all_actors = graphene.List(ActorType)
actor = graphene.Field(
ActorType,
id=graphene.Int(),
)
def resolve_all_actors(self, args, context, info):
return Actor.objects.all()
def resolve_actor(self, args, context, info):
id = args.get('id')
return Actor.objects.get(id=id)
It is for GraphQL to know how data in django look like. I have Query
which is a way of saying to GraphQL that we want to ask for either all
actors or for specific one. I also add a handy shortcut from
graphene_django
called DjangoObjectType
- all I need is to provide a
model and it will know which field particular model has.
I got also resolve_actor
& resolve_all_actors
so I can either query
for all of them in GraphQL. I can go to http://127.0.0.1:8000/graphql
and execute:
query{
allActors{
lastName
}
}
to get response:
{
"data": {
"allActors": [
{
"lastName": "Travolta"
},
{
"lastName": "Jackson"
},
{
"lastName": "Thurman"
},
{
"lastName": "Foxx"
},
{
"lastName": "Waltz"
},
{
"lastName": "DiCaprio"
},
{
"lastName": "Pitt"
},
{
"lastName": "Laurent"
},
{
"lastName": "Russell"
},
{
"lastName": "Leigh"
}
]
}
}
or for one actor:
{
actor(id: 1) {
firstName
lastName
}
}
and get:
{
"data": {
"actor": {
"firstName": "John",
"lastName": "Travolta"
}
}
}
Exact the same thing I did for films application - schema looks like this:
class FilmType(DjangoObjectType):
actors = graphene.List(ActorType)
class Meta:
model = Film
@graphene.resolve_only_args
def resolve_actors(self):
return self.actors.all()
class Query(graphene.AbstractType):
all_films = graphene.List(FilmType)
film = graphene.Field(
FilmType,
id=graphene.Int(),
)
def resolve_all_films(self, args, context, info):
return Film.objects.all()
def resolve_film(self, args, context, info):
id = args.get('id')
return Film.objects.get(id=id)
Everything here is almost the same but I got many to many relation in a
database between Film
& Actor
model. In order for GraphQL to
understand it I need to use decorator resolve_only_args
. As the name
suggests function wrapped inside decorator will be resolved using
arguments passed - in this case, I will be Film instance so I can get
all actors that played in this movie:
{
film(id: 1) {
title
actors {
firstName
lastName
}
}
}
{
"data": {
"film": {
"title": "Pulp Fiction",
"actors": [
{
"firstName": "John",
"lastName": "Travolta"
},
{
"firstName": "Samuel L.",
"lastName": "Jackson"
},
{
"firstName": "Uma",
"lastName": "Thurman"
}
]
}
}
}
The last bit missing is external api which I implemented in a way to cache as much as I can:
URL = "https://api.themoviedb.org/3/search/movie"
class ExternalMovie(object):
session = requests.Session()
def __init__(self, title):
self._payload = {'api_key': settings.TMDB_API_KEY, 'query': title}
@cached_property
def description(self):
response = self.session.get(URL, data=self._payload)
response.raise_for_status()
return response.json()['results'][0]['overview']
I use cached_property
so next calls via GraphQL will be cached. Schema
to this is very simple:
class Query(graphene.AbstractType):
description = graphene.String(title=graphene.String())
def resolve_description(self, args, context, info):
title = args.get('title')
external_movie = ExternalMovie(title=title)
return external_movie.description
and allows me to query for description:
{
description((title: "Pulp Fiction"));
}
{
"data": {
"description": "..."
}
}
That’s all for today! Feel free to comment - was this blog post helpful? Was something missing?
Repo with code can be found on github.