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.
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
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
Now write some queries in the graphql editor at http://127.0.0.1:8000/graphql/
Run the query by clicking on the run button to see the output.
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:
Last Word
Here we didn't work on changing the data(Mutations), you are to explore that part 😜.
Top comments (1)
Please we need tutorials on django graphql social auth