E-Commerce GraphQL API with Django | Python
At the time of writing this I think I have a feeling that this article will be a long one. If you’re questioning whom this article is for I can say that this is for those who have knowledge of Django and wants to build GraphQL APIs with Django.
I want to mention that I have not pre-planned this project. So I think we will implement other features also while building this.
Setup The Project
Without making this more longer lets get into actual development. Open up a terminal and navigate to any directory you prefer. Follow these commands:
mkdir ecommerce_graphql
cd ecommerce_graphql
pipenv shell
pipenv install django
django-admin startproject core .
Look carefully that I have added a dot(.) at the end of startproject command. If everything goes ok your directory structure now should look like this
ecommerce_graphql
├── core
│ ├── asgi.py
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── manage.py
├── Pipfile
└── Pipfile.lock
Plan The Database Tables Visually
Now it is a good time to plan which data we need. I prefer to design the database tables in this site → dbdesigner.net
Here’s my database plan. You can always view it here → click

Note that while developing this project I may have change the designs so it may look different when you’re reading it. You can always view the latest version on the link given above.
Code The Models In Django Project
Time to code the models. So far I’ve created the django project but I don’t have any apps now. So lets first create a app.
python manage.py startapp products
Lets add this new app to settings.py file.
# core/settings.py
....INSTALLED_APPS = [
....
'products',]
Now go inside products/models.py file and add the following
# products/models.pyfrom django.db import models
from django.contrib.auth.models import Userclass Category(models.Model):
name = models.CharField(max_length=50)class SubCategory(models.Model):
name = models.CharField(max_length=50)
category = models.ForeignKey(Category,
related_name='subCategories',
on_delete=models.CASCADE)class Product(models.Model):
name = models.CharField(max_length=100)
category = models.ForeignKey(Category,
related_name='products',
on_delete=models.CASCADE)
subCategory = models.ForeignKey(SubCategory,
related_name='products',
on_delete=models.CASCADE)
description = models.TextField()
price = models.PositiveIntegerField()
amount_in_stock = models.PositiveIntegerField()class ProductImage(models.Model):
product = models.ForeignKey(Product,
related_name='images',
on_delete=models.CASCADE)
image = models.ImageField(upload_to='productImages/')class Rating(models.Model):
product = models.ForeignKey(Product,
related_name='ratings',
on_delete=models.CASCADE)
stars = models.PositiveIntegerField()
note = models.TextField()
rating_from = models.ForeignKey(User,
related_name='ratings_given',
on_delete=models.CASCADE)
created_on = models.DateTimeField(auto_now_add=True)class Comment(models.Model):
product = models.ForeignKey(Product,
related_name='comments',
on_delete=models.CASCADE)
comment_from = models.ForeignKey(User,
related_name='comments_given',
on_delete=models.CASCADE)
body = models.TextField()
created_on = models.DateTimeField(auto_now_add=True)
As you can see I have added Category, SubCategory, Product, ProductImage, Rating & Comment model. SubCategory has foreign key to Category that means one category can have many sub-categories but on sub-category can only have one category. Same logic goes for other models. Inside ProductImage model I have used a ImageField, so now I have to install pillow package. Also I will also make the migrations and migrate the changes to database.
pipenv install pillow
python manage.py makemigrations
python manage.py migrate
Before continuing further, we should set media configs in our settings.py file. In the bottom of the core/settings.py file add this →
# core/settings.py MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media/'
Also we should edit our core/urls.py file to add the media urls →
# core/urls.py...
from django.conf import settings #new
from django.conf.urls.static import static #newurlpatterns = [
....
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Good for now. Lets build the other apps
python manage.py startapp orders
python manage.py startapp carts
You may think there was no need to create apps for these. But later in the future if you want to add more logic and models like OrderTransaction you can do so. Add these new apps to settings.py file
# core/settings.py
....INSTALLED_APPS = [
....
'products',
'orders', #new
'carts', #new]
Lets quickly code the models.py file inside orders app
# orders/models.pyfrom django.db import modelsfrom products.models import Product
from django.contrib.auth.models import UserSTATUS_CHOICES = [
('created', 'Created'),
('processing', 'Processing'),
('cancelled', 'Cancelled'),
('delivered', 'Delivered'),
]class Order(models.Model):
product = models.ForeignKey(Product,
related_name='orders',
on_delete=models.CASCADE)
order_from = models.ForeignKey(User,
related_name='orders_given',
on_delete=models.CASCADE)
status = models.CharField(max_length=20,
default="created",
choices=STATUS_CHOICES)
created_on = models.DateTimeField(auto_now_add=True)
I have used choices for the status field in Order model. Inside STATUS_CHOICES you can view that I have given values in pairs. First value is the one which will be saved on database and second value is the human readable form.
Now lets complete the models inside carts app
# carts/models.pyfrom django.db import modelsfrom products.models import Product
from django.contrib.auth.models import Userclass Cart(models.Model):
account = models.OneToOneField(User,
related_name='cart',
on_delete=models.CASCADE)class CartItem(models.Model):
cart = models.ForeignKey(Cart,
related_name='items',
on_delete=models.CASCADE)
product = models.ManyToManyField(Product,
related_name='product')
amount = models.PositiveIntegerField(default=1)
Inside carts/models.py I have created two models. One is Cart and another one is CartItem. CartItem has ForeignKey relation with Cart and ManyToMany relation with Product. That means one product come under many carts and one cart can have many products. And the amount field is to refer how many units of a specific item we have in cart.
Now migrate the new models to database:
python manage.py makemigrations
python manage.py migrage
Add New Models to Admin Panel
Now that we have our models set, we can query our database. But before that I want to visually see the models and objects inside the admin panel. So to do that open up products/admin.py file and register the models.
# products/admin.pyfrom django.contrib import adminfrom .models import (
Category,
SubCategory,
Product,
ProductImage,
Rating,
Comment,
)admin.site.register(Category)
admin.site.register(SubCategory)
admin.site.register(Product)
admin.site.register(ProductImage)
admin.site.register(Rating)
admin.site.register(Comment)
Next open orders/admin.py file and again register the models
# orders/admin.pyfrom django.contrib import adminfrom .models import Orderadmin.site.register(Order)
Now open carts/admin.py and do the same
# carts/admin.pyfrom django.contrib import adminfrom .models import Cart, CartItemadmin.site.register(Cart)
admin.site.register(CartItem)
Now that we have every models registered, go ahead to your terminal and create a new super user →
python manage.py createsuperuser
Start the server and in your browser go to http://127.0.0.1:8000/admin/. You should see all the models that we registered.

I will suggest you to now go ahead and add some dummy data.
Query The Data with GraphQL
Time for the actual part of this article → how do we request data from backend in graphql form. First install the graphene-django package →
pipenv install graphene-django
After that go to your settings.py file and inside INSTALLED_APPS section add the new package
# settings.py
...
INSTALLED_APPS = [
...
'graphene_django', # new
]
The thing you have to understand is that in the case of usual REST APIs we send request to different urls with various kinds of methods like GET, POST, PUT, DELETE. But in the case of graphql we only need to request to one URL . And another major difference is that in graphql we have only two kinds of methods Query and Mutation. Query is to get data from backend and Mutation is used to send, update, delete data in the backend. Lets see how that actually works in django.
First add the only URL we need. Inside core/urls.py file add this →
# core/urls.py
...
from graphene_django.views import GraphQLView #newurlpatterns = [
...
path("graphql/", GraphQLView.as_view(graphiql=True)), #new] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Next up we have added a schema. Inside schema, we define the types of different kind of objects. Add a new file inside products directory and name it schema.py →
# products/schema.pyimport graphene
from graphene_django import DjangoObjectTypefrom .models import (
Category,
SubCategory,
Product,
ProductImage,
Rating,
Comment,
)class ProductType(DjangoObjectType):
class Meta:
model = Productclass Query(graphene.ObjectType):
all_products = graphene.List(ProductType) def resolve_all_products(root, info):
return Product.objects.all()
At the top we have imported the things we need. Then we declared a ProductType class and inside its meta class we passed Product as model. This is very similar how django forms works. Then we created a Query class. Inside it we said that we expect a query ‘all_products’. And then we defined a resolve function for ‘all_products’. We said that whenever a all_products query comes return all the products. OK! I know this sounds very confusing but hang on with me, you’ll get it when we define more queries and types.
Now that we have a schema inside products we need to import that to core/schema.py. (we don’t have the core/schema.py file yet). We do that because later we will add more schema inside other apps and we want all the schema to be contained by one core schema. So lets add a new file inside core directory and name it schema.py →
# core/schema.pyimport grapheneimport products.schemaclass Query(products.schema.Query, graphene.ObjectType):
passschema = graphene.Schema(query=Query)
We have our core schema file. We need to tell django that we want to use this schema for all the queries that come. To do so go inside the settings.py file and add this at the bottom of the file →
# settings.py
...
GRAPHENE = {
"SCHEMA": "core.schema.schema"
}
Everything is ready. Run the server and go to http://127.0.0.1:8000/graphql/ You should see a two graphql panels. Left one is for input and right one is for output. You can remove all the comments from the input panel.
We can now query of all products and can select what fields we want in return. That’s the beauty of GraphQL. We can mention what fields we want in the time of fetching. That leads to no over-fetching and no under-fetching of data. Lets query for all the products and for every product we want its id, name, price and description.

As you can see graphql makes all the queries in camel case type. We have defined all_products inside products/schema.py but it becomes allProducts while fetching. If we need to get for example amount_in_stock for every product we have just add amountInStock inside the allProductsQuery.
Time to add other queries. Go inside your products/schema.py file and add these classes after the imports →

I have added type classes for category, subcategory, product’s image, rating and comment. Also I have restricted some of the types by providing fields, if we don’t do that frontend user can request product data from ratings query because of relations. If that happens it can create a loop . And another thing is that in ProductImageType we defined resolve_image additionally so that it always returns full path of the image file when requested.
Now I can query like this →

Ok Great! But we need add some more queries. add these inside the Query class in products/schema.py :

added query to get individual product by its id, then we can get individual category or subCategory by their id or name. Here’s an example →

See how easy it is to get data about different fields. We can change any data requirement whenever we want from the frontend without chaning anything in the backend. That’s when the true advantage of GraphQL is visible clearly. If we wanted to do the exact same thing with REST we will have to write serializers, url paths for every different data requests.
NOTE
There’s more! In this article I have just described the Query and Object Types. But if you want to create, update or delete data you should definitely learn about graphql mutations. You should also checkout authentication and authorization topics with Django graphene. These links will help you out →
If you find articles like this valuable and want to support this type of content, consider signing up to Medium, You will get unlimited access to all articles from thousands of authors. I appreciate your support. Made with ❤️ by Sk Soyeb Akhter.