Build GraphQL Web APIs with Django Graphene -- Words (1547)

Django

On the previous tutorial we have introduced GraphQL to build Web APIs .In this tutorial we are going to build a real world example web application with Django ,which makes use of GraphQL and its Python implementation ,Graphene .

Tutorial Parts :

Building Better Django WEB APIs with GraphQL Tutorial

Build GraphQL Web APIs with Django Graphene

Lets start by creating a new virtual environment and install required packages including django ,

Head over to your terminal and enter :

virtualenv graphqlenv 
source graphqlenv/bin/activate 

This will create a new virtual environment and activate it .

Next install django and graphene packages with pip

pip install django 
pip install graphene_django

You can also install graphiql_django which provides a user interface for testing GraphQL queries against your server .

pip install graphiql_django

Next lets create a Django project with a single application :

python django-admin.py startproject inventory . 
cd inventory
python manage.py startapp inventory 

Open settings.py and add inventory and graphenedjango to installedapps array :

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'graphene_django',
    'inventory'
]

Then create your database :

python manage.py migrate 

Create models

Open inventory/models.py then add :

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models

class Product(models.Model):

    sku = models.CharField(max_length=13,help_text="Enter Product Stock Keeping Unit")
    barcode = models.CharField(max_length=13,help_text="Enter Product Barcode (ISBN, UPC ...)")

    title = models.CharField(max_length=200, help_text="Enter Product Title")
    description = models.TextField(help_text="Enter Product Description")

    unitCost = models.FloatField(help_text="Enter Product Unit Cost")
    unit = models.CharField(max_length=10,help_text="Enter Product Unit ")

    quantity = models.FloatField(help_text="Enter Product Quantity")
    minQuantity = models.FloatField(help_text="Enter Product Min Quantity")

    family = models.ForeignKey('Family')
    location = models.ForeignKey('Location')


    def __str__(self):

        return self.title


class Family(models.Model):

    reference = models.CharField(max_length=13, help_text="Enter Family Reference")
    title = models.CharField(max_length=200, help_text="Enter Family Title")
    description = models.TextField(help_text="Enter Family Description")

    unit = models.CharField(max_length=10,help_text="Enter Family Unit ")

    minQuantity = models.FloatField(help_text="Enter Family Min Quantity")


    def __str__(self):

        return self.title


class Location(models.Model):


    reference = models.CharField(max_length=20, help_text="Enter Location Reference")
    title = models.CharField(max_length=200, help_text="Enter Location Title")
    description = models.TextField(help_text="Enter Location Description")

    def __str__(self):

        return self.title


class Transaction(models.Model):

    sku = models.CharField(max_length=13,help_text="Enter Product Stock Keeping Unit")
    barcode = models.CharField(max_length=13,help_text="Enter Product Barcode (ISBN, UPC ...)")

    comment = models.TextField(help_text="Enter Product Stock Keeping Unit")

    unitCost = models.FloatField(help_text="Enter Product Unit Cost")

    quantity = models.FloatField(help_text="Enter Product Quantity")

    product = models.ForeignKey('Product')

    date = models.DateField(null=True, blank=True)

    REASONS = (
        ('ns', 'New Stock'),
        ('ur', 'Usable Return'),
        ('nr', 'Unusable Return'),
    )


    reason = models.CharField(max_length=2, choices=REASONS, blank=True, default='ns', help_text='Reason for transaction')

    def __str__(self):

        return 'Transaction :  %d' % (self.id)

Next create migrations and appy them :

python manage.py makemigrations
python manage.py migrate

Adding an Admin Interface

The next thing is to add these models to the admin interface so we can add some data :

Open inventory/admin.py and add

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.contrib import admin

from .models import Product ,Family ,Location ,Transaction  
# Register your models here.

admin.site.register(Product)
admin.site.register(Family)
admin.site.register(Location)
admin.site.register(Transaction)

Next create a login to be able to access the admin app

python manage.py createsuperuser 

Enter username and password when prompted and hit enter .

Now run your web application with :

python manage.py runserver

Then go to http://127.0.0.1:8000/admin with your browser ,login and submit some data for created models .

Adding GraphQL support : Schema and Object Types

To be able to execute GraphQL queries against our web application we need to add a Schema ,Object Types and a view function which takes GraphQL queries .

Create app schema.py

Create inventory/schema.py then add

We first create a subclass of DjangoObjectType for each model we want to query with GraphQL

import graphene

from graphene_django.types import DjangoObjectType

from .models import Family , Location , Product , Transaction 

class FamilyType(DjangoObjectType):
    class Meta:
        model = Family 

class LocationType(DjangoObjectType):
    class Meta:
        model = Location 

class ProductType(DjangoObjectType):
    class Meta:
        model = Product 

class TransactionType(DjangoObjectType):
    class Meta:
        model = Transaction

Then we create a an abstract query ,a subclass of AbstractType .It's abstract because it's an app level query . For each app you have you need to create an app level abstract query and then combine all abstract queries with a concrete project level query .

You need to create a subclass of List members for each DjangoObjectType then create resolve_xxx() methods for each Query member

class Query(graphene.AbstractType):
    all_families = graphene.List(FamilyType)
    all_locations = graphene.List(LocationType)
    all_products = graphene.List(ProductType)
    all_transactions = graphene.List(TransactionType)

    def resolve_all_families(self, args, context, info):
        return Family.objects.all()

    def resolve_all_locations(self, args, context, info):
        return Location.objects.all()

    def resolve_all_products(self, args, context, info):
        return Product.objects.all()

    def resolve_all_transactions(self, args, context, info):
        return Transaction.objects.all()

Create project level schema.py

Next create a project level Query .

Create a project level schema.py file then add :

import graphene

import inventory.schema 


class Query(inventory.schema.Query, graphene.ObjectType):
    # This class extends all abstract apps level Queries and graphene.ObjectType
    pass

schema = graphene.Schema(query=Query)

So we first create a Query class which extends all abstract queries and also ObjectType then we create a graphene.Schema object which takes Query class as parameter .

Now we need to add a GRAPHINE config object in settings.py

GRAPHENE = {
    'SCHEMA': 'product_inventory_manager.schema.schema'
} 

Create GraphQL view

With GraphQL you don't need multiple endpoint but just one so lets create one

Open urls.py then add

from django.conf.urls import url
from django.contrib import admin

from graphene_django.views import GraphQLView

from product_inventory_manager.schema import schema

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^graphql', GraphQLView.as_view(graphiql=True)),
]

We have previously installed a GraphQL package for adding a user interface to test GraphQL queries so if you want to enable it you just set the graphiql parameter to True : graphiql=True

Serving app and testing GraphQL

Now you are ready to test the GraphQL API so start by serving your Django app :

python manage.py runserver 

Then go to localhost:8000/graphql and run some queries :

query {
allProducts {
    id
    sku
}
}   

You should get something like the following , depending on the data you have :

{
"data": {
    "allProducts": [
    {
        "id": "1",
        "sku": "Product001"
    }
    ]
}
}   

You can experiment with other models and you can also add fields but how do you know the name of the query ? It's simple just take the name of the field you create in the abstract query and transform it to camel case .

For example

all_families = graphene.List(FamilyType) => allFamilies

all_locations = graphene.List(LocationType) => allLocations

all_products = graphene.List(ProductType) => allProducts

all_transactions = graphene.List(TransactionType) => allTransactions

Then for each query specify the model fields you want to retrieve .

You can also query relationships .

Suppose we want all families with their products ,you just need to tell GraphQL what you need

query {
allFamilies {
    id
    reference 
    productSet {
        id
        sku 
    }
}
}

For my case I got :

{
"data": {
    "allFamilies": [
    {
        "id": "1",
        "reference": "FM001",
        "productSet": [
        {
            "id": "1",
            "sku": "Product001"
        }
        ]
    },
    {
        "id": "2",
        "reference": "FM001",
        "productSet": []
    }
    ]
}
}

Now what if you need the parent family and location of each product ,that's also easy doable with GraphQL :

query {
    allProducts {
        id
        sku 
        family {
            id
        }
        location {
            id
        }

    }
}

Querying for single items

We have seen how query all items but what if we need just one item by its id for example ,how can we achieve that ?

Go back to your abstract query in app schema.py file then update to be able to query for a single product

Add

product = graphene.Field(ProductType,id=graphene.Int())

Then a resolve_xxx() method

def resolve_product(self, args, context, info):
    id = args.get('id')

    if id is not None:
        return Product.objects.get(pk=id)

    return None

Now you can query for a single product by its id

query {
product(id: 1) {
    sku
    barcode
}
}

In the same way you can add support for getting single families , locations and transactions .

Conclusion


GraphQL is a very powerful technology for building Web APIs and thanks to Django Graphene you can easily add support for GraphQL to your django project .

You can find the code it this GitHub repository



mrnerd is a Web Entrepreneur & Developer, Digital Marketer and a Passionate Blogger. He writes about software and website development,money making tips and SEO etc.. He is a tech addict and a computer geek with a lot of hobbies.