DEV Community

Cover image for Introducing GraphQL ft. Django
Revanth Reddy
Revanth Reddy

Posted on

Introducing GraphQL ft. Django

Hey There 👋!

Hope everyone is safe and healthy. It's been 4 months since I was stuck at home because of the CoVid19 pandemic. So, during these times, while I was working on a REST Api for one of my Application, I encountered an article regarding GraphQL and that really impressed me and led me to write this post. In traditional REST APIs, we create URLs for all the data objects that are needed to be accessed. If we wanted to create a Question-Answer Forum - we'll need URLs for all Questions, URL for each question and URL for all answers of that specific question. So we are to do a lot of requests for such related data.

And what if the user is experiencing a slow internet connection?

Here comes GraphQL into the play. GraphQL isn't an API architecture like REST. It is a Query language to retrieve and manipulate related data in an easier way.

Alt Text

History

Having its origin from Facebook, GraphQL has finally moved into the hands of GraphQL Foundation.

Let's create an application for Question-Answer using GraphQL, Django.

Let's Go 🚀

Schema

Creating Types

Types depict the data that the data object possess.

Consider:

type Answer {
  id: ID!
  answer: String!
}

type Question {
  id: ID!
  question: String!
  answers: [Answer]
}
Enter fullscreen mode Exit fullscreen mode

The ID type tells us that the field is the unique identifier for that type of data(also known as primary key).

Note: The exclamation mark ! signifies that the field is required.

Here we have primitive type String (others being Int, Char)
and also the custom type Answer

The list type is enclosed in square brackets([Answer])

Queries

A Query is a question that we ask in to retrieve the data:

type Query {
  question(id: ID!): Question
  answer(id: ID!): Answer
  answers: [Answer]
  questions: [Question]
}
Enter fullscreen mode Exit fullscreen mode

The above query will retrieve Question and Answer using their ID, and also Question list, Answer list without any filter

Mutations

Mutation tells about the operations that can be done to change the data.
Mutations need two things:

  • Inputs: It is used to send an entire object rather than the individual fields.
input AnswerInput {
  id: ID
  answer: String!
}

input QuestionInput {
  id: ID
  question: String!
  answers: [AnswerInput]
}
Enter fullscreen mode Exit fullscreen mode
  • Payloads: These depict the output of a mutation(just like a response from an API Endpoint)
type AnswerPayload {
  ok: Boolean
  answer: Answer
}

type QuestionPayload {
  ok: Boolean
  question: Question
}
Enter fullscreen mode Exit fullscreen mode

Here ok field is similar to status in an API response.

And Mutation binds inputs & payloads together:

type Mutation {
  createAnswer(input: AnswerInput) : AnswerPayload
  createQuestion(input: QuestionInput) : QuestionPayload
  updateAnswer(id: ID!, input: AnswerInput) : AnswerPayload
  updateQuestion(id: ID!, input: QuestionInput) : QuestionPayload
}
Enter fullscreen mode Exit fullscreen mode

Schema

Finally, we build the schema. It contains both the queries and mutations

schema {
    query: Query
    mutation: Mutation
}
Enter fullscreen mode Exit fullscreen mode

Python & GraphQL

GraphQL is platform-independent, and its users are free to use any language(Python, Java, PHP, Go ...), frameworks(Node, Rails, Django).

In Python we need not use GraphQL's syntax for creating the schema, we can directly use python but for that, we need a library called Graphene.

Let's get into Action 👨‍💻

It's a good practice to maintain a virtual environment, requirements file and gitignore file.

Let's create a folder and then initialize a virtual environment myvenv(you can use your virtual environment name) in it.

mkdir quorum && cd quorum
python3 -m venv myvenv
Enter fullscreen mode Exit fullscreen mode

Now let's activate the virtual environment myvenv.

source myvenv/bin/activate
Enter fullscreen mode Exit fullscreen mode

Note: To exit the virtual environment, type deactivate in the terminal.

Installing packages

Inside the virtual environment, we use pip to install the required libraries:

pip install django
pip install graphene_django
Enter fullscreen mode Exit fullscreen mode

And we create a Django project:

django-admin startproject qproj .
Enter fullscreen mode Exit fullscreen mode

A django project can have many applications. Apps are like the components within a project which can be reused.

Let's create an app inside this project.

python manage.py startapp qapp
Enter fullscreen mode Exit fullscreen mode

After creating the app we need to tell Django that it should use it. We do that in the file qproj/settings.py --open this folder in your code editor and open settings.py file.

We need to find INSTALLED_APPS and add graphene_django & qapp
so finally it should look like this:

...
...
INSTALLED_APPS = [
    'graphene_django',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'qapp',
]
...
...
Enter fullscreen mode Exit fullscreen mode

Creating Models

In the qapp/models.py file we define all objects called Models – this is a place in which we will define our Question and Answer models.

Let's open qapp/models.py in the code editor, remove everything from it, and write code like this:

from django.db import models

class Question(models.Model):
    question = models.CharField(max_length=100)

    def __str__(self):
        return self.question

class Answer(models.Model):
    answer = models.CharField(max_length=100)
    question = models.ForeignKey(Question, on_delete=models.CASCADE)

    def __str__(self):
        return self.answer
Enter fullscreen mode Exit fullscreen mode

Now we have the blueprint of our models but we need to imprint that in our database. In django this thing is carried out by making migrations and migrating them.

python manage.py makemigrations
python manage.py migrate
Enter fullscreen mode Exit fullscreen mode

Now add a superuser(admin) who can handle the admin panel using:

python manage.py createsuperuser
Enter fullscreen mode Exit fullscreen mode

In qapp/admin.py we register our models so that admin can access these models:

from django.contrib import admin
from .models import Question, Answer

admin.site.register(Question)
admin.site.register(Answer)
Enter fullscreen mode Exit fullscreen mode

Now run server using python manage.py runserver and visit http://127.0.0.1:8000/admin or http://localhost:8000/admin to see the admin dashboard.
Add the relevant data to questions and answers.

Schema with Graphene

Queries

In qapp folder, create a new file called schema.py and define it as follows:

import graphene
from graphene_django.types import DjangoObjectType, ObjectType
from .models import Question, Answer

# Create a GraphQL type for the Answer model
class AnswerType(DjangoObjectType):
    class Meta:
        model = Answer

# Create a GraphQL type for the Question model
class QuestionType(DjangoObjectType):
    class Meta:
        model = Question
Enter fullscreen mode Exit fullscreen mode

In the same file add the following Queries:

# Create a Query
class Query(ObjectType):
    question = graphene.Field(QuestionType, id=graphene.Int())
    answer = graphene.Field(AnswerType, id=graphene.Int())
    questions = graphene.List(ActorType)
    answers= graphene.List(AnswerType)

    def resolve_answer(self, info, **kwargs):
        id = kwargs.get('id')
        if id is not None:
            return Answer.objects.get(pk=id)
        return None

    def resolve_question(self, info, **kwargs):
        id = kwargs.get('id')
        if id is not None:
            return Question.objects.get(pk=id)
        return None

    def resolve_answers(self, info, **kwargs):
        return Answer.objects.all()

    def resolve_questions(self, info, **kwargs):
        return Question.objects.all()
Enter fullscreen mode Exit fullscreen mode

Here we have written how the query works: as a single object using an ID and as a list.

Schema

Add this to the end of qapp/schema.py

schema = graphene.Schema(query=Query)
Enter fullscreen mode Exit fullscreen mode

Registering the schema

To make this API available through the project we need to register and for that we need to import it in qproj.
So create a new schema.py in qproj folder and the code:

import graphene
import qapp.schema

class Query(qapp.schema.Query, graphene.ObjectType):
    # This class will inherit from multiple Queries
    # as we begin to add more apps to our project
    pass

schema = graphene.Schema(query=Query)
Enter fullscreen mode Exit fullscreen mode

In settings.py add the GRAPHENE schema. You can add it anywhere inside it. I'm adding it between INSTALLED_APPS and MIDDLEWARE.

GRAPHENE = {
    'SCHEMA':'qproj.schema.schema'
}
Enter fullscreen mode Exit fullscreen mode

Unlike naive APIs, data can be accessed using only one endpoint in GraphQL. Let's create that url endpoint as graphql/ in urls.py

Open qproj/urls.py and modify the code to:

from django.contrib import admin
from django.urls import path
from graphene_django.views import GraphQLView
from .schema import schema

urlpatterns = [
    path('admin/', admin.site.urls),
    path('graphql/', GraphQLView.as_view(graphiql=True)),
]
Enter fullscreen mode Exit fullscreen mode

Test Test Test

To check the API, we need to run the project. In terminal run the following code:

python manage.py runserver
Enter fullscreen mode Exit fullscreen mode

If there are no errors you can see a server running at port 8000 as follows

Alt Text

Once the server is ready goto http://127.0.0.1:8000/graphql/. You'll find a GraphQl editor. In this playground you can write queries, mutations(we didn't create mutations yet).

Building Queries

In the admin panel I have added some relevant Questions and answers
Alt Text
Alt Text

Now write some queries in the graphql editor at http://127.0.0.1:8000/graphql/

Alt Text
Run the query by clicking on the run button to see the output.
Alt Text

Till now, we have are doing the querying part in graphql ide but what if you want to do it as a POST request
An application communicating with your API would send POST requests to the /graphql endpoint. Before we can make POST requests from outside the Django project, we need to change urls.py as:

from django.contrib import admin
from django.urls import path
from graphene_django.views import GraphQLView
from .schema import schema
from django.views.decorators.csrf import csrf_exempt # New library

urlpatterns = [
    path('admin/', admin.site.urls),
    path('graphql/', csrf_exempt(GraphQLView.as_view(graphiql=True))),
]
Enter fullscreen mode Exit fullscreen mode

You can turn off the graphql editor(Graphiql) by replacing graphiql=True with graphiql=False

CSRF means (Cross-Site Request Forgery)protection- an inbuilt feature of Django to prevent unauthenticated/ improperly authenticated users from doing evil things 👻.
Though it's useful it would prevent external applications from interacting with the API. To counter the scarf exemption you might consider authentication or a more secured way to access the API.

Now enter the following curl request in a new terminal keeping the server active

curl -X POST -H "Content-Type: application/json" --data '{ "query": "{ questions { question } }" }' http://127.0.0.1:8000/graphql/
Enter fullscreen mode Exit fullscreen mode

You'll get a result something like this:
Alt Text

Last Word

Here we didn't work on changing the data(Mutations), you are to explore that part 😜.

Code References:

GitHub Repo


Revanth Reddy's GitHUB Profile


Revanth Reddy's DEV Profile

Top comments (1)

Collapse
 
aliplutus profile image
aliplutus

Please we need tutorials on django graphql social auth