GraphQL API in Django with CRUD operations

Internship at OpenGenus

Get FREE domain for 1st year and build your brand new site

Hi readers, this article will cover the following topics:

  1. What is GraphQL API?
  2. Using GraphQL in Django
  3. CRUD Operations on for users in Django

NOTE: This article assumes that you are already familiar with Django framework.

What is GraphQL?

You might have heard of the phrase GraphQL API if you play with APIs, but what is it actually?

GraphQL was develeoped at facebook when they needed a data fetching api for their mobile app powerful enough to handle millions of traffic per second.

Professionally speaking, "GraphQL is an open-source query and data manipulation language for APIs".

It means it's simply a query or an API request(similar to REST-API request) but it's a smart query, because it contains additional information about the data requested from the API server, and thats what makes it efficient.

How to use GraphQl in your Django project

In order to integrate GraphQL API functionality to your Django app, you need to use a third party module which you can install by simply pip install graphene-django. There are other clients as well which you can find here.

In this article, we are gonna build a full user-management GraphQL API so lets get started.

Installing Dependencies:

Assuming you already have your virtual environment and Django project setup, we can go ahead and install the dependencies:

To use GraphQL in Django we are gonna install following dependencies:

  • A GraphQL django version which has some extended functionality inbuilt for Django
    • pip install graphene-django
    • You can find its docs here
  • A GUI interface through which we can interact with API
    • pip install django-graphiql
    • You can find its docs here

After installing the dependencies, first thing we would do is install those apps in our Django project. This is as simple as we do with any other app in django, just add them in INSTALLED_APPS list.

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

    'users',
    
    'django_graphiql',  # -> For GUI interface
    'graphene_django',  # -> For graphql api
]

Run the migrations after installing those apps: python manage.py migrate and your django app is ready to be used GraphQL functionality.

Structure of GraphQL API in Django

There are a few things that might be new to you but you have to keep those in mind.

GraphQL uses queries and mutations for CRUD operations. Wait, what are they?

Query:

Just like a normal REST api query, its the request we send to fetch data from server, but this one is a smart query as it tells your server what and how much data it has to return for a request and where to look for it.

At the very basic level, a graphql query should look like this:

REQUEST:
-----------
   query {
       # Queries can have comments
        user(id:"1"){  # Providing arguments
            firstName
            lastName
            email
            username
            id
        }
    }
    
RESPONSE:
-----------
    {
      "data": {
        "user": {
          "firstName": "FooUser",
          "lastName": "MyLastname",
          "email": "someEmail@gmail.com",
          "username": "admin",
          "id": "1"
        }
      }
    }

You see how we are fetching only the data we asked for instead of fetching all data.

Structure of Query

graphql_schema-2

The query type contains a resolve method for each argument of data that can be requested. This resolve method describes the logic behind the fetching of data from the data base.

Remember the name resolve is important and must be followed by the name of field you want to return the data for.

Example:


        class QueryType(graphene.ObjectType):
            name = "query"  # Optional
            description = "..."  # Optional

            pet = graphene.String()

            def resolve_pet(root, args, info):
                return "Dog"

        schema = graphene.Schema(query = QueryType)

Mutations

GraphQL has similar analogy like REST that any query can be used to modify data(even GET), but conventionally this is not recommended.
It's useful to establish a convention that any query that modifies the data must be sent explicitly via a mutation.

Just like queries, mutation field returns an object type. This can be useful for fetching new state of an object after update.

Structure of Mutations
gql_mutations

Each model type contains a mutate() class method in which you implement the logic of modifying a django models object.

To make a mutation in model, we need to create a model type in which we will get the information or fields we need to modify, a field of object type, and a mutate() method which will contain the logic of any operation we want to perform to modify the data.

    class UpdateUser(graphene.Mutation):
    
    ...
    
    @classmethod
    def mutate(cls, root, info, update_data, id):
        user = USER.objects.filter(id=id)
        if user:
            params = vars(update_data)
            user.update(**{k: v for k, v in params.items() if params[k]})

            return UpdateUser(user=user.first())
        else:
            print('error')

Schema: A schema will describe our data models and what data can be queried or requested. Each of these schema has a resolve method in which you implement the logic of getting data from database.

A schema, you can say is an entry point in your API where every operations(CRUD) takes place.

Create a file name schema.py in your app, this will be the entry point in your api. Lets say I made schema.py in my users app.

To keep it simple, Consider the following hello world example:


    class QueryType(graphene.ObjectType):
        name = "query"  # Optional
        description = "..."  # Optional

        name = graphene.String()

        def resolve_hello(root, args, info):
            return "SomeName"

    schema = graphene.Schema(query = QueryType)

Now before you can use GUI interface to interact with your api, we need to define our schema and tell django_graphiql where is it. We do this by setting a variable in our project's settings.py as follows:

In settings.py


    GRAPHENE = {
        'SCHEMA': 'users.schema.schema',
    }

Here is the output:
graphql_query_output

Making Schema Using Django Model:

To use django models in our schema, we need to make a class for our model specifying fields and other necessary details.

Structure of schema:


    import graphene
    from graphene_django import DjangoObjectType

    from .models import User


    class UserType(graphene.ObjectType):
        name = "query"  # Optional
        description = "..."  # Optional
        
        # Fields 
        first_name = graphene.String()
        last_name = graphene.String()
        email = graphene.String()
        username = graphene.String()
        id = graphene.String()


    class Query(graphene.ObjectType):
        name = "query"  # Optional
        description = "..."  # Optional

        person = graphene.Field(
            UserType,
            id=graphene.String()
        )

        @staticmethod
        def resolve_person(root, info, **args):
            id = args.get('id')
            return userModel.objects.get(pk=id)


    schema = graphene.Schema(query=Query)

graphql_user-1

CRUD Operations Using GraphQL in Django

Similar to REST, creating CURD in GraphQL is very easy.
The directory structure of my project is as follows:


├── gqlApi
│   ├── asgi.py
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── manage.py
└── users
    ├── admin.py
    ├── apps.py
    ├── __init__.py
    ├── models.py
    ├── schema.py  # -> Schema file
    ├── tests.py
    ├── urls.py
    └── views.py

In setting.py

    GRAPHENE = {
        'SCHEMA': 'users.schema.schema',
    }

Create User

To create user, we need the details of the user that we want to create. This is passed using a POST request to mutations in your api.

To implement create operation, we need three classes to work with

  • UserType (which contains the important fields of user table and model)
  • CreateUser (which contains creation of user logic)
  • Mutations

In our schema.py file create a class to create new users with the fields required as follows:

from graphene_django import DjangoObjectType
import graphene
from django.contrib.auth import get_user_model

User = get_user_model()

class UserType(DjangoObjectType):
    class Meta:
        model = User
        fields = '__all__'

class CreateUser(graphene.Mutation):
    """
    This is the main class where user object is created.
    This class must implement a mutate method.
    """
    class Arguments:
        username = graphene.String()
        email = graphene.String()
        first_name = graphene.String()
        last_name = graphene.String()
        password = graphene.String()

    user = graphene.Field(UserType)

    @classmethod
    def mutate(cls, root, info, **user_data):
        user = User(
            first_name=user_data.get('first_name'),
            username=user_data.get('username'),
            email=user_data.get('email'),
        )
        user.set_password(user_data.password)  # This will hash the password
        
        user.save()
        return CreateUser(user=user)

class Mutation(graphene.ObjectType):
    """
    This class contains the fields of models that are supposed to be 
    mutated.
    """
    create_user = CreateUser.Field()

schema = graphene.Schema(
    mutation=Mutation  # Adding mutations to our schema
)

In Action:
create_operation

Update User

Similar to create user, we are gonna define our update logic in UpdateUser class's mutate() method.

The logic of update is simple in mutate() method, iterate over thee given dict of parameters and include only those which are not None.

Note: .update() method will only work if you get the user object by .filter() which returns a query. Other than that, you need to explicitly pass keyworded arguments and then save using save() method.

In our schema.py file:

    ...
    
    class UpdateUser(graphene.Mutation):
        class Arguments:
            id = graphene.ID()
            username = graphene.String()
            email = graphene.String()
            first_name = graphene.String()
            last_name = graphene.String()


        user = graphene.Field(UserType)

        @classmethod
        def mutate(cls, root, info, id, **update_data):
            user = User.objects.filter(id=id)
            if user:
                params = update_data
                user.update(**{k: v for k, v in params.items() if params[k]})
                return UpdateUser(user=user.first())
            else:
                print('User with given ID does not exist.')
                
                
    class Mutation(graphene.ObjectType):
        """
        This class contains the fields of models that are supposed to be 
        mutated.
        """
        create_user = CreateUser.Field()
        update_user = UpdateUser.Field()

In Action:
update_opearion

Read User Data

Reading data in GraphQL is not a part of mutations as it does not effect anny data so it will be simply a query. You can ask for specific details from your api server.

To implement read operation in graphql, simply create a field of User type. Add any extra key worded arguments you would like to receive in resolve method to get user from data base.

For example in code below, id and username will be passed as key worded arguments in resolve method and based on them, you can fetch the user model from database.

Now the main work begins in resolve method, here you got the information how to get data and what you need to return. You can simply use django api for that as shown below:

    ...
    
    class QueryType(graphene.ObjectType):
        """
        This is what read query looks like:
            query {
                  user(id or username like-> username:"boopDog") {
                    firstName
                    lastName
                    ... -> fetch fields
                  }
                }
        """
        user = graphene.Field(
            UserType,
            id=graphene.String(),
            username=graphene.String()
        )

        @staticmethod
        def resolve_user(*args, **kwargs):
            return User.objects.filter(**kwargs).first()


schema = graphene.Schema(
    query=QueryType,
    mutation=Mutation
)

In Action:

read_operation

Delete User:

Delete operation should be implemented using mutations as it modifies the data in database.
There is nothing much to implement in delete operation, similar to read, pass the information about the user you want to delete and simply delete using .delete()

    ...
    
    class DeleteUser(graphene.Mutation):
        class Arguments:
            id = graphene.ID()

        user = graphene.Field(UserType)

        @classmethod
        def mutate(cls, root, info, id):
            user = USER.objects.get(id=id)
            user.delete()
            return DeleteUser(user)
    
    
     

In Action:
delete_operation

Who Uses GraphQL

  • Facebook
  • GitHub
  • Coursera

This concludes our discussion on GraphQL API and how to use it in Django app. Hope you find this article at OpenGenus useful.

Nitin Sharma

Nitin Sharma

Hi, I am a recent graduate who is specialized in computer science. I have a very good knowledge of python. I use C++ for implementing algorithms and data structures.

Read More

Improved & Reviewed by:


OpenGenus Foundation OpenGenus Foundation