Yezy Ilomo
Posted on October 31, 2019
Since being introduced by Facebook, GraphQL has taken the API world by storm as an alternative to REST APIs. GraphQL fixes many problems that API developers and users have found with RESTful architecture. However, it also introduces a new set of challenges which need to be evaluated. Because GraphQL is not simply a evolutionary replacement for RESTful APIs, this post will show you a package which takes all the goods from GraphQL and put them into a RESTful architecture so that developers can benefit from both approaches. The name of the package is django-restql and it's developed on top of Django REST Framework but the idea can be implemented on any other framework.
As stated on its documentation, django-restql is a python library which allows you to turn your API made with Django REST Framework(DRF) into a GraphQL like API. With django-restql you will be able to
Send a query to your API and get exactly what you need, nothing more and nothing less.
Control the data you get, not the server.
Get predictable results, since you control what you get from the server.
Save the load of fetching unused data from the server(Over-fetching and Under-fetching problem).
Write(create & update) nested data of any level with flexibility.
Isn't it cool?.
Requirements
- Python >= 3.5
- Django >= 1.10
- Django REST Framework >= 3.5
Installing
pip install django-restql
Querying Data
Using django-restql to query data is very simple, you just have to inherit the DynamicFieldsMixin
class when defining a serializer.
from rest_framework import serializers
from django.contrib.auth.models import User
from django_restql.mixins import DynamicFieldsMixin
class UserSerializer(DynamicFieldsMixin, serializer.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'email', 'groups']
A regular request returns all fields as specified on DRF serializer, in fact django-restql doesn't handle this request at all. Below is an example of a request without a query parameter, as you see all fields are retured as specified on UserSerializer
.
GET /users
[
{
"id": 1,
"username": "yezyilomo",
"email": "yezileliilomo@hotmail.com",
"groups": [1,2]
},
...
]
django-restql handle all GET requests with query
parameter, this parameter is the one used to pass all fields to be included in a response. For example to select id
and username
fields from User model, send a request with a query
parameter as shown below.
GET /users/?query={id, username}
[
{
"id": 1,
"username": "yezyilomo"
},
...
]
django-restql support querying both flat and nested resources, so you can expand or query nested fields at any level as long as your field is defined as nested field on a serializer. For example you can query a country and region field from location.
GET /users/?query={id, username, location{country, region}}
[
{
"id": 1,
"username": "yezyilomo",
"location": {
"contry": "Tanzania",
"region": "Dar es salaam"
}
},
...
]
django-restql got your back on querying iterable nested fields(one2many or many2many) too. For example if you want to expand groups
field into id
and name
, here is how you would do it.
GET /users/?query={id, username, groups{id, name}}
[
{
"id": 1,
"username": "yezyilomo",
"groups": [
{
"id": 2,
"name": "Auth_User"
}
{
"id": 3,
"name": "Admin_User"
}
]
},
...
]
If a query contains nested field without expanding and it's not defined as a nested field on a serializer, django-restql will return its id or array of ids for the case of nested iterable field(one2many or many2many). For example on a request below location
is a flat nested field(many2one) and groups
is an iterable nested field(one2many or many2many).
GET /users/?query={id, username, location, group}
[
{
"id": 1,
"username": "yezyilomo",
"location": 6,
"groups": [1,2]
},
...
]
Using fields=[..]
and exclude=[..]
kwargs
With django-restql you can specify fields to be included when instantiating a serializer, this provides a way to refilter fields on nested fields(i.e you can opt to remove some fields on a nested field). Below is an example which shows how you can specify fields to be included on nested resources.
from rest_framework import serializers
from django.contrib.auth.models import User
from django_restql.mixins import DynamicFieldsMixin
from app.models import Book, Course
class BookSerializer(DynamicFieldsMixin, serializers.ModelSerializer):
class Meta:
model = Book
fields = ['id', 'title', 'author']
class CourseSerializer(DynamicFieldsMixin, serializers.ModelSerializer):
books = BookSerializer(many=True, read_only=True, fields=["title"])
class Meta:
model = Course
fields = ['name', 'code', 'books']
GET /courses/
[
{
"name": "Computer Programming",
"code": "CS50",
"books": [
{"title": "Computer Programming Basics"},
{"title": "Data structures"}
]
},
...
]
As you see from the response above, the nested resource(book) has only one field(title) as specified on fields=["title"]
kwarg during instantiating BookSerializer, so if you send a request like GET /course?query={name, code, books{title, author}}
you will get an error that author
field is not found because it was not included on fields=["title"]
kwarg.
You can also specify fields to be excluded when instantiating a serializer by using exclude=[]
as shown below
from rest_framework import serializers
from django_restql.mixins import DynamicFieldsMixin
from app.models import Book, Course
class BookSerializer(DynamicFieldsMixin, serializers.ModelSerializer):
class Meta:
model = Book
fields = ['id', 'title', 'author']
class CourseSerializer(DynamicFieldsMixin, serializers.ModelSerializer):
books = BookSerializer(many=True, read_only=True, exclude=["author"])
class Meta:
model = Course
fields = ['name', 'code', 'books']
GET /courses/
[
{
"name": "Computer Programming",
"code": "CS50",
"books": [
{"id": 1, "title": "Computer Programming Basics"},
{"id": 2, "title": "Data structures"}
]
},
...
]
From the response above you can see that author
field has been excluded fom book nested resource as specified on exclude=["author"]
kwarg during instantiating BookSerializer.
Note: fields=[..]
and exclude=[]
kwargs have no effect when you access the resources directly, so when you access books you will still get all fields i.e
GET /books/
[
{
"id": 1,
"title": "Computer Programming Basics",
"author": "S.Mobit"
},
...
]
So you can see that all fields have appeared as specified on fields = ['id', 'title', 'author']
on BookSerializer class.
Using return_pk=True
kwargs
With django-restql you can specify whether to return nested resource pk or data. Below is an example which shows how we can specify fields to be included on nested resources.
from rest_framework import serializers
from django_restql.mixins import DynamicFieldsMixin
from app.models import Book, Course
class BookSerializer(DynamicFieldsMixin, serializers.ModelSerializer):
class Meta:
model = Book
fields = ['id', 'title', 'author']
class CourseSerializer(DynamicFieldsMixin, serializers.ModelSerializer):
books = BookSerializer(many=True, read_only=True, return_pk=True)
class Meta:
model = Course
fields = ['name', 'code', 'books']
GET /course/
[
{
"name": "Computer Programming",
"code": "CS50",
"books": [1,2]
},
...
]
So you can see that on a nested field books
book pks have been returned instead of books data as specified on return_pk=True
kwarg on BookSerializer
.
django-restql can do more than just querying data, you can use it to do data mutation too(creating and updating data), it even has support for nested data mutation, you can read more about this on its documentation here.
Posted on October 31, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.