Introducing GraphQL ft. Django
Tags: graphql, django, healthydebate, python
Published: 2020-08-14 06:55:33

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](https://dev-to-uploads.s3.amazonaws.com/i/w85dpaoas2zqn12ly3hi.png)

##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]
}
```
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]
}
```
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]
}
```
* `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
}
```
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
}
```
###Schema
Finally, we build the schema. It contains both the queries and mutations
```
schema {
    query: Query
    mutation: Mutation
}
```
##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
```
Now let's activate the virtual environment myvenv.
```
source myvenv/bin/activate
```
*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
```
And we create a Django project:
```
django-admin startproject qproj .
```
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
```

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',
]
...
...
```

###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
```
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
```
Now add a superuser(admin) who can handle the admin panel using:
```
python manage.py createsuperuser
```

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)
```
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
```


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()
```

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)
```
###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)
```
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'
}
```
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)),
]
```
#Test Test Test
To check the API, we need to run the project. In terminal run the following code:
```
python manage.py runserver
```
If there are no errors you can see a server running at port 8000 as follows

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/0wx9vyxpnt9taio8xmyn.png)

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](https://dev-to-uploads.s3.amazonaws.com/i/f8u9gk110a2i2j2o6m1u.png)
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/4ncjd1v3ynmnn5ygphq8.png)

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

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/2v8acgw6e3cd0uaba3bb.png)
Run the query by clicking on the run button to see the output.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/q330ptp381ui3usd979y.png)

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))),
]
```

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/
```
You'll get a result something like this:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/vqhxhraloogvwc21dj9j.png)

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

#Code References:
[GitHub Repo](https://github.com/revanth-reddy/graphql-django)


<a  href="https://github.com/revanth-reddy">
<img  src="https://octodex.github.com/images/daftpunktocat-thomas.gif"  alt="Revanth Reddy's GitHUB Profile"  height="100"  width="100">
</a>



<a  href="https://revanth.tech">
<img  src="https://octodex.github.com/images/Fintechtocat.png"  alt="Revanth Reddy's DEV Profile"  height="100"  width="100">
</a>