loading...

REST + Some GraphQL with Django REST Framework

yezyilomo profile image Yezy Ilomo ・5 min read

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.

Discussion

pic
Editor guide