Leverage Turing Intelligence capabilities to integrate AI into your operations, enhance automation, and optimize cloud migration for scalable impact.
Advance foundation model research and improve LLM reasoning, coding, and multimodal capabilities with Turing AGI Advancement.
Access a global network of elite AI professionals through Turing Jobs—vetted experts ready to accelerate your AI initiatives.
GraphQL is gaining popularity and many consider it a successor to REST APIs. If you're familiar with Python and Django, and want to get started with GraphQL or just to get a taste, this guide is definitively for you. You're going to learn more about how to use GraphQL with Python and Django to build a simple CRUD API for a notes app (like google keep). That tutorial assumes that you're already familiar with Python and Django.
This article will help in developing your knowledge and prepare you for any Django jobs interview!
GraphQL is a data query and manipulation language for APIs, and a runtime for executing queries using a type system you define for your data.
It allows retrieving exactly what data we need in a nested, convenient, and highly flexible way. It also works with only one URL to perform all actions.
To have a glance, suppose you want to fetch an article and a paginated list of comments using REST, you will probably make two requests:
1. get to "/article/<article-id>" to retrieve the article:
json { "id": 5, "preview": "Blah blah blah", "fullContent": "Full Blah blah blah", }
2. get "/comments/<article-id>/?page=1" to retrieve comments:
json { "id": 5, "content": "Comment here," }
But with GraphQL you can just do this
gql query { article (articleId) { id preview fullContent comments(PaginationInfosHere) { id content } } }
Now let's suppose you want to render only the preview of each article on some pages and the full content and comments on another page. You just can't do it using REST, you're always obliged to get all the data a REST API exposes to you.
Here is the beauty and flexibility of GraphQL. You could fetch exactly what you need:
gql query { article (articleId) { id preview } }
And all that using the same URL.
You can also go as deep as you want to retrieve exactly what you need as data.
Many developers believe that GraphQL will replace REST. Many others have already discovered that GraphQL helps solve some of the common challenges developers face when creating REST APIs.
However, deciding to opt for GraphQL comes with several considerations. GraphQL offers several advantages that make it a compelling option for API development.
One of the key benefits is its ability to overcome the limitations of REST about data fetching. In REST, it is common for clients to either receive more data than they need (overfetching) or not enough data (underfetching) because the only way to download data is by accessing pre-defined endpoints that return fixed data sets. GraphQL allows clients to specify the exact data they want in a single request, making it easier to design APIs that meet their specific needs.
GraphQL uses a type system to define the capabilities of an API. The types exposed in an API are defined in a schema using the GraphQL Schema Definition Language (SDL) and/or through code. This allows for strong typing and a clear understanding of the data that can be queried from the API.
Frontend teams can use a typed GraphQL API with confidence, knowing that if the backend team makes any changes to the API design, they will receive instant feedback when a query is raised from the frontend. This enables better collaboration and ensures that the frontend and backend are always in sync.
Tools like the GraphQL Code Generator can also automatically generate code for queries and mutations based on the GraphQL query files in the codebase, which speeds up development and reduces the risk of errors in production.
A common pattern with REST APIs is to structure the endpoints according to the different views in the app (e.g. /menu, /prices, /images, etc.). This is convenient because it allows the client to retrieve all the necessary data for a given view by accessing the corresponding endpoint. However, this approach can be inflexible and hinder rapid iteration. Every time the UI is changed, there is a risk that the data requirements will change, requiring the backend to be adjusted as well. This can be time-consuming and slow down product development.
In contrast, the flexible nature of GraphQL enables changes on the client side to be made without any extra work on the server. Since clients can specify their exact data requirements, no backend adjustments are needed when the frontend design or data needs changes.
One key difference between GraphQL and other API technologies is its ability to stitch multiple schemas into a single schema that can be accessed by the client. This allows for a more flexible and modular approach to API design and implementation. You can combine multiple independent schemas into a unified API without having to write custom code or make other complex changes. This simplifies the API development process and makes it easier to manage and maintain.
The main difference between GraphQL and REST APIs is that GraphQL is a specification and query language, while REST is an architectural concept for network-based software.
Now, let's put our hands to work using this theory!
First, let’s present what we call in graphQL jargon the 'schema'. It’s just the set of the different components of a GraphQL API:
The common package to create a GraphQL API with Python and Django is graphene.
I'm going to use virtualenv but you can go with what you're comfortable with,
poetry or conda or whatever tool it is:
bash virtualenv venv source venv/bin/activate
bash pip install django
bash django-admin startproject graphql_notes_api
bash cd graphql_notes_api
bash django-admin startapp notes
bash pip install graphene-django
python INSTALLED_APPS = [ ... 'django.contrib.staticfiles', # Add lines below "notes", "graphene_django" ]
python ... from django.views.decorators.csrf import csrf_exempt # Add this line from graphene_django.views import GraphQLView # Add this lineurlpatterns = [ path('admin/', admin.site.urls), path("graphql", csrf_exempt(GraphQLView.as_view(graphiql=True))), # Add this line ]
"graphiql=True" is set to "True" because we want to use "graphiql" (an internal
web interface to test the GraphQL API).
pythongraphql_notes_api/notes/models.py
from django.db import models
class Category(models.Model): name = models.CharField(max_length=100)
def __str__(self): return self.name
class Note(models.Model): content = models.TextField() category = models.ForeignKey( Category, related_name="notes", on_delete=models.CASCADE )
def __str__(self): return self.content[:10] + '...' if len(self.content) > 10 else ''</pre><ul><li>create migrations</li></ul><pre>bash
python manage.py makemigrations
bash python manage.py migrate
Create this file "graphql_notes_api/notes/fixtures/notes.json"
json [ { "fields": { "name": "Personal" }, "model": "notes.category", "pk": 1 }, { "fields": { "name": "Work" }, "model": "notes.category","pk": 2 }, { "fields": { "category": 1, "content": "I like good old eggs" }, "model": "notes.note", "pk": 1 }, { "fields": { "category": 1, "content": "I have a yoga session tomorrow" }, "model": "notes.note", "pk": 2 }, { "fields": { "category": 2, "content": "I have a meeting with my boss next friday" }, "model": "notes.note", "pk": 3 }, { "fields": { "category": 2, "content": "We need to release the MVP before June." }, "model": "notes.note", "pk": 4 }
]
bashpython manage.py loaddata notes
Installed 6 object(s) from 1 fixture(s)
Now we are moving to the interesting part. We are going to set up the schema:
pythongraphql_notes_api/schema.py
import graphene from graphene_django import DjangoObjectType
from notes.models import Category, Note
class CategoryType(DjangoObjectType): class Meta: model = Category fields = ("id", "name")
class NoteType(DjangoObjectType): class Meta: model = Note fields = ("id", "name", "content")
class Query(graphene.ObjectType): all_notes = g:aphene.List(NoteType) category_by_name = graphene.Field(CategoryType, name=graphene.String(required=True))
def resolve_all_notes(root, info): return Note.objects.select_related("category").all() def resolve_category_by_name(root, info, name): try: return Category.objects.get(name=name) except Category.DoesNotExist: raise Exception('Invalid category Id')
schema = graphene.Schema(query=Query)
bashgraphql_notes_api/settings.py
GRAPHENE = { "SCHEMA": "schema.schema" }
And we are done, and no, I'm not joking, our graphQL API is ready :relieved:.
Let's test it out.
bashpython manage.py runserver
Watching for file changes with StatReloader Performing system checks...
System check identified no issues (0 silenced). December 12, 2022 - 21:39:16 Django version 3.2.16, using settings 'graphql_notes_api.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C.
You should see an interface like that:
gql query { allNotes { id content } }
You should have as output:
gql { "data": { "allNotes": [ { "id": "1", "content": "I like good old eggs" }, { "id": "2", "content": "I have a yoga session tomorrow" }, { "id": "3", "content": "I have a meeting with my boss next friday" }, { "id": "4", "content": "We need to release the MVP before June." } ] } }
Nice, you have just created a working graphQL API!
We can also query for relations. And that is also a power of graphQL.
For example, we can list all notes related to a given category like this:
gql query { categoryByName(name: "Personal") { id name notes { id content } } }
And here should be the output:
gql { "data": { "categoryByName": { "id": "1", "name": "Personal", "notes": [ { "id": "1", "content": "I like good old eggs" }, { "id": "2", "content": "I have a yoga session tomorrow" } ] } } }
Add the following code to "schema" file
pythongraphql_notes_api/schema.py
from graphql import GraphQLError # add this line ... class NoteCreationMutation(graphene.Mutation): class Arguments: # The input arguments for this mutation content = graphene.String(required=True) category_id = graphene.ID(required=True)
# This exposes the created `note` in the response to the mutation note = graphene.Field(NoteType) @classmethod def mutate(cls, root, info, content, category_id): # We first make sure that the Id is valid try: category=Category.objects.get(id=category_id) except Category.DoesNotExist: raise Exception('Invalid category Id') note = Note.objects.create(content=content, category=category) # Notice we return an instance of `Note` because the mutation expose # a `NoteType` in the response as seen above. return NoteCreationMutation(note=note)
class NoteDeletionMutation(graphene.Mutation): class Arguments: note_id = graphene.ID(required=True)
# This exposes a boolean to know the status of the deletion. deleted = graphene.Boolean(default_value=False) @classmethod def mutate(cls, root, info, note_id): try: note=Note.objects.get(id=note_id) except Note.DoesNotExist: raise Exception('Invalid note Id') note.delete() return NoteDeletionMutation(deleted=True)
class Mutation(graphene.ObjectType): create_note = NoteCreationMutation.Field() delete_note = NoteDeletionMutation.Field()
schema = graphene.Schema(query=Query, mutation=Mutation)
Note that we defined the "Mutation" root class and added it the schema "mutation=Mutation" right after the query.
Go back on graphiql and run:
gql mutation{ createNote(categoryId:1, content: "My first real note") { note { id content category { id name } } } }
You should have:
gql { "data": { "createNote": { "note": { "id": "5", "content": "My first real note", "category": { "id": "1", "name": "Personal" } } } } }
Run again the query and you will notice that our new note has been created.
Just run the following mutation
gql mutation { deleteNote(noteId: 5) { deleted } }
In our case, the newly created note has an Id of 5.
You should get:
gql { "data": { "deleteNote": { "deleted": true } } }
Handle pagination and filtering with Graphene Relay
The last thing we want to show here is graphene relay.
Relay is a JavaScript framework designed to improve the performance, maintainability, and type safety of GraphQL data management. It was initially made for React applications. We want to show you one of the most important uses of relay for building API: pagination.
We could have handled the pagination manually by defining the page count and offset in the query and using it in the resolver, but that would have been less elegant as relay does it automatically and in a much cleaner way.
Let start:
bash pip install django-filter
pythongraphql_notes_api/schema.py
Add these imports at the top of the file
from graphene import relay, ObjectType from graphene_django import DjangoObjectType from graphene_django.filter import DjangoFilterConnectionField ... ...
Graphene will automatically do a mapping between Category model's fields onto
CategoryNode type.
The configuration is made in the Meta class below
class CategoryNode(DjangoObjectType): class Meta: model = Category filter_fields = ['name'] interfaces = (relay.Node, )
class NoteNode(DjangoObjectType): class Meta: model = Note # Setup more advanced filtering here filter_fields = { 'content': ['exact', 'icontains', 'istartswith'], 'category': ['exact'], 'category__name': ['exact'], } interfaces = (relay.Node, ) ... ... class Query(graphene.ObjectType): ... # Add the lines below category = relay.Node.Field(CategoryNode) all_categories_relay = DjangoFilterConnectionField(CategoryNode)
note = relay.Node.Field(NoteNode) all_notes_relay = DjangoFilterConnectionField(NoteNode)</pre><p>That's all. Let's test it:</p><ul><li>Run</li></ul><pre>gql
query { allNotesRelay { edges { node { id, content } } } }
You should have this output:
{ "data": { "allNotesRelay": { "edges": [ { "node": { "id": "Tm90ZU5vZGU6MQ==", "content": "I like good old eggs" } }, { "node": { "id": "Tm90ZU5vZGU6Mg==", "content": "I have a yoga session tomorrow" } }, { "node": { "id": "Tm90ZU5vZGU6Mw==", "content": "I have a meeting with my boss next friday" } }, { "node": { "id": "Tm90ZU5vZGU6NA==", "content": "We need to release the MVP before June." } } ] } } }
Note: The "id" present in each node is auto-generated by the relay and is unique across all objects.
Now we can specify a custom "id"
gql query { note(id: "Tm90ZU5vZGU6Mg==") { content } }
You'll have this output:
{ "data": { "note": { "content": "I have a yoga session tomorrow" } } }
gql query {You can also use
category: "CATEGORY GLOBAL ID"
allNotesRelay(content_Icontains: "yoga", category_Name: "Personal") { edges { node { content } } } }
Output:
{ "data": { "allNotesRelay": { "edges": [ { "node": { "content": "I have a yoga session tomorrow" } } ] } } }
Final congratulations, we are at the end of this guide.
Don't hesitate to learn more about GraphQL on the official documentation and start using it to build awesome projects.
GraphQL allows you to build any kind of API, and being more flexible than REST, it lets you fetch exactly what you want using the same URL. But it comes with the price of being a little more complex than REST. However, in this tutorial, we have shown how Python and Django makes it easier to implement GraphQL. We started by explaining what GraphQL is, then compared it to REST, and finally created a simple GraphQL API that covers core GraphQL topics like types, queries, mutations, relay using the Django Framework and graphene.
Software engineer with more than 3 years of experience in FullStack web development and a technical writer in my spare time.I have relevant experience working on Python, Django, React, VueJS, GraphQL.