Create a Fullstack app with Django, GraphQL and VueJS (Part 1)

Create a Fullstack app with Django, GraphQL and VueJS (Part 1)

  • 364

How to Create a Fullstack app with Django, GraphQL and VueJS(Part1)

As a data engineer, most of my experience and knowledge mainly centers around building databases, architecting data platforms and most importantly building fault tolerant data pipelines (either within Hadoop clusters or on single Linux instances). I originally started learning Django about 4 years ago because of my need to visualize a lot of the data that I was working with within web pages (especially tables and charts).

This is image title

As time went on, I began to see the importance of being able to control the flow of data from acquisition all the way to visualization and that propelled me to get some more experience in working with javascript and frontend frameworks.

This is image title
Example Workflow (with GraphQL and VueJS)

This tutorial introduces the concept of building and using a django backend to serve your VueJS application using GraphQl (Graphene) as your API framework where we’ll be creating an employee management system for a fictional startup.

For this part 1, we’ll create the backend for the management system using Django and integrate the graphene (graphQL) framework so that we can add new employees, edit current employee information like name and titles and even delete employees who no longer work there.

In part 2, we’ll create the VueJS application which we will use to query the GraphQL APIs to display our employees.

PREREQUISITES

This tutorial assumes that you have:

  • An intermediate level understanding of Django and its Model-View-Template (MVT) pattern.
  • A beginner level understanding of VueJS
  • Prior knowledge of GraphQL or Graphene is not required

You will also need to have the following installed on your computer

  • Python 3 (Python 2 will also suffice but python 3 is recommended)
  • Pipenv
  • Node & Npm
  • A text editor to create and edit your code (I would suggest a multi language editor like Sublime Text)

CREATING THE DJANGO BACKEND

We’ll start by opening a terminal and navigating to a base directory on your computer where you want to starting building from.

  1. Create a new directory and change into that directory
mkdir startupql && cd startupql

2). Install django using pipenv

pipenv install django

*Note that this will install the latest version of django. If you are more comfortable with a specific of django, you should specify it the version number you want e.g. django==1.11

The pipenv command will create and activate a virtual environment, install its flavor of pip and any other python packages your specify (In this case, django)

3). We can now switch to the pipenv shell so that run commands directly within the virtual environment

pipenv shell

You should now see the name of your virtual environment within brackets along with the prompt

(startupql) bash-3.2$

*You may not necessarily see this exact prompt on your computer

4). Create the django project and the application for the management system

(startupql) bash-3.2$ django-admin.py startproject startupql .
(startupql) bash-3.2$ django-admin.py startapp company

5). Your directory structure should look similar to this

├── Pipfile
├── Pipfile.lock
├── company
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── manage.py
└── startupql
 ├── __init__.py
 ├── settings.py
 ├── urls.py
 └── wsgi.py

6). For every django project, one of the first things you need to do after creating your project is to run migrations and also run the server to make sure that there are no issues with the initial installation

python manage.py migrate
python manage.py runserver

7). You should now be able to see your django application at the localhost. For django, it will either be http://localhost:8000 or http://127.0.0.1:8000

This is image title

8). The next step will be to go into the project level settings.py file in order to make the project aware of the company app

INSTALLED_APPS = [
 ##django apps
 ‘company’,
]

9). Now it is time to create our models!

# company/models.py
from django.db import models
# City where employees live
class City(models.Model):
    city_name = models.CharField(max_length=100)
    def __str__(self):
        return self.city_name
# Employee title
class Title(models.Model):
    title_name = models.CharField(max_length=100)
    def __str__(self):
        return self.title_name
class Employee(models.Model):
    employee_name = models.CharField(max_length=255)
    employee_city = models.ForeignKey(City, related_name=’employee_city’, on_delete=’cascade’)
    employee_title = models.ForeignKey(Title, related_name=’employee_title’, on_delete=’cascade’)
    def __str__(self):
       return self.employee_name

From the snippet above, you can see that we have 3 models (City, Title & Employee). The assumption is that the employees for this company don’t all live in the same city hence the City model. The Title model will hold all defined titles within the company and the Employee model will contain details of each employee along with their respective cities and titles.

Now we have to create a migration file and sync our database with the new model

(startupql) bash-3.2$ python manage.py makemigrations company
(startupql) bash-3.2$ python manage.py migrate

10). In order to be able to see the new models in the django admin section, we need to add them to the admin.py file in the project directory

from django.contrib import admin
from .models import *
class CityAdmin(admin.ModelAdmin):
    fields = (‘city_name’,)
class TitleAdmin(admin.ModelAdmin):
    fields = (‘title_name’,)
class EmployeeAdmin(admin.ModelAdmin):
    fields = (‘employee_name’, ‘employee_city’, ‘employee_title’,)
admin.site.register(City, CityAdmin)
admin.site.register(Title, TitleAdmin)
admin.site.register(Employee, EmployeeAdmin)

Don’t forget that you need to create a superuser account in order to access the django admin

(startupql) bash-3.2$ python manage.py createsuperuser
Username: myusername
Email address: [email protected]
Password: 
Password (again): 
Superuser created successfully.

11). Run your server again, navigate to http://127.0.0.1:8000/admin and log in using the superuser username and password you just created

This is image title

12). We can use this opportunity to manually enter a few values for the City and Title models so that we can get a baseline.

This is image title

This is image title

Let’s only add only two entries for the Employee model for now. We’ll add some more later

This is image title

This is image title

That’s it for the django backend for now! On to Graphene!

CREATING THE GRAPHENE (GRAPHQL) FRAMEWORK

Before we dive in the Graphene framework, I want to do a quick introduction on GraphQL. GraphQL is an API query language developed at Facebook and was built to be an improvement on the REST architecture.

One of the biggest advantages of GraphQL over REST is the ability to make one request (or query) and return data from multiple schemas at once (similar to running a SQL join query) whereas one would have to make multiple requests in REST to achieve the same results.

This is image title

Another advantage of GraphQL is that developers do not have to manage multiple endpoints. With GraphQL, there is only one endpoint which is used to make and run all your queries.

An important note to remember is the terminology used in GraphQL. With GraphQL, a “query” is the equivalent to a GET request while a “mutation” is equivalent to a POST/PUT request.

Graphene is a library built for developers to interact with GraphQL within python projects. There are integrations built specifically for SQLAlchemy and Django (which we will be using for this project)

13). In order to use graphene with django, we need to install the graphene framework

pipenv install graphene-django

We also need to install GraphiQL which is the GraphQL IDE located in the browser which we will need to create and run queries

pipenv install django-graphiql

Let’s also install django filter, this will be explained in the future

pipenv install django-filter

Lastly, we need to update the settings.py file in the project directory with our graphene installation

INSTALLED_APPS = [
 ##django apps
 ‘graphene_django’,
 ‘company’,
]

**Note that it is graphene_django in the settings file while it is graphene-django in the Pipfile

You also need to add this snippet to the bottom of the settings.py file. This allows graphene to identify the schema file needed for queries and mutations.

GRAPHENE = {
 ‘SCHEMA’: ‘startupql.schema.schema’
}

**Make sure to change startupql to the name of your django project

14). Now to the fun part…creating our GraphQL schemas. Create a schema.py file within your app directory (i.e. company/schema.py)

import graphene
from graphene_django import DjangoObjectType
from .models import *
from graphene_django.filter import DjangoFilterConnectionField
class CityNode(DjangoObjectType):
    class Meta:
        model = City
        filter_fields = [‘city_name’]
        interfaces = (graphene.relay.Node,)
class TitleNode(DjangoObjectType):
    class Meta:
        model = Title
        filter_fields = [‘title_name’]
        interfaces = (graphene.relay.Node,)
class EmployeeNode(DjangoObjectType):
    class Meta:
        model = Employee
        filter_fields = [
              ‘employee_name’,
              ‘employee_city__city_name’,
              ‘employee_title__title_name’
               ]
        interfaces = (graphene.relay.Node,)
class Query(object):
    city = graphene.relay.Node.Field(CityNode)
    all_cities = DjangoFilterConnectionField(CityNode)
    title = graphene.relay.Node.Field(TitleNode)
    all_titles = DjangoFilterConnectionField(TitleNode)
    employee = graphene.relay.Node.Field(EmployeeNode)
    all_employees = DjangoFilterConnectionField(EmployeeNode)

We created a new Object Type for all the models that we want to be able to query from the graphene framework.

At the bottom of the page, we also create a Query class which represents the root type through which access to all the nodes begin.

14). We also need to create another schema file in the project level directory. The schema file refers to the module setting that we specified above (‘startupql.schema.schema’). Graphene needs this to be able to identify all the app level schemas within our django project.

import graphene
import company.schema
class Query(company.schema.Query, graphene.ObjectType):
    pass
schema = graphene.Schema(query=Query)

15). Lastly, you need to add the url path for the GraphiQL IDE. Navigate to the project level urls.py file and add the following url pattern to it

from django.contrib import admin
from django.urls import path
from graphene_django.views import GraphQLView
urlpatterns = [
     path(‘admin/’, admin.site.urls),
     path(‘graphql/’, GraphQLView.as_view(graphiql=True)), #add this
]

Let’s see if it worked. Run the server again and navigate to http://127.0.0.1:8000/graphql. If everything worked right, you should see the screen below

This is image title

16). Now we’re ready to run some quick queries!

To return all employees that we manually entered earlier

This is image title

To return all the city entries

This is image title

Success!!

17). You will notice in the app level schema file, we have some filter fields for each graphene object. Graphene uses django filter (which we installed earlier) to refine our queries and only return results which meet our criteria

To return the City result for “Chicago”

This is image title

To return Employee results using filters from a foreign key, you will need to traverse to the related model using an underscore (_) between the field names. E.g. employeeCity_CityName)

This is image title

MOVING TO ADVANCED REQUESTS

Now that we’ve gotten our hands dirty with graphql and django, let’s switch gears to more advanced queries. In this section, we will create new entries, update existing entries and delete entries.

Creating Entries

Remember that we currently only have 3 Title and 2 Employee entries in our system. Let’s add some more entries to fill out the company database.

18). Let’s start by adding some titles to the company organization. We will navigate to your app level schema file and create a Mutation schema. Remember I mentioned earlier that the create function in graphQL is called a mutation (equivalent of a POST request in REST)

class CreateTitle(graphene.relay.ClientIDMutation):
    title = graphene.Field(TitleNode)
    class Input:
        title_name = graphene.String()
    def mutate_and_get_payload(root, info, **input):
        title = Title(
            title_name=input.get(‘title_name’)
        )
        title.save()
    return CreateTitle(title=title)

In the snippet above, we are initiating a title variable to represent the Title node which in turn virtually represents the Title model. Within that variable, we create an Input class to describe all the node and model columns that we want to be able to create new values for. We then create the ‘mutate_and_get_payload’ function where we get the ‘title_name’ column the model and save the entry to the model. This is a simple example but we see a slightly more advanced example soon when we create a mutation class for the Employee model.

Within the same app level schema file, we also want to create a Mutation root class right below the Query class.

class Query(object):
    city = graphene.relay.Node.Field(CityNode)
    all_cities = DjangoFilterConnectionField(CityNode)
    title = graphene.relay.Node.Field(TitleNode)
    all_titles = DjangoFilterConnectionField(TitleNode)
    employee = graphene.relay.Node.Field(EmployeeNode)
    all_employees = DjangoFilterConnectionField(EmployeeNode)
class Mutation(graphene.AbstractType):
    create_title = CreateTitle.Field()

Lastly, we want to go to the project level schema file and create another Mutation class which will combine all of our all level mutations and also add the mutation to the schema variable

import graphene
import company.schema
class Query(company.schema.Query, graphene.ObjectType):
    pass
class Mutation(company.schema.Mutation, graphene.ObjectType):
    pass
schema = graphene.Schema(query=Query, mutation=Mutation)

Now let’s run our server again and navigate to the graphql IDE and create a new “CIO” title entry

This is image title

Let’s confirm that it’s been created by running a query to return all titles

This is image title

19). Let’s repeat the process for the Employee model

class CreateEmployee(graphene.relay.ClientIDMutation):
    book = graphene.Field(EmployeeNode)
    class Input:
        employee_name = graphene.String()
        employee_city = graphene.String()
        employee_title = graphene.String()
    def mutate_and_get_payload(root, info, **input):
        employee = Employee(
           employee_name=input.get(‘employee_name’),
           employee_city=City.objects.get(
              city_name=input.get(‘employee_city’)),
          employee_title=Title.objects.get(
             title_name=input.get(‘employee_title’))
        )
        employee.save()
        return CreateEmployee(employee=employee)

In this snippet, you will notice that in the Input class, we had to define all of the columns for the Employee model. Additionally, within the ‘mutate_and_get_payload’ function, you will notice that we have to create a relative reference to the City and Title models given the foreign key relationship.

Remember to add the class to the Mutation root at the bottom of the app level schema file

class Mutation(graphene.AbstractType):
    create_title = CreateTitle.Field()
    create_employee = CreateEmployee.Field()

There’s no need to update the project level schema file since the mutation only needs to be defined once.

Now we’ll create a new employee entry using the newly created title.

This is image title
Updating Entries

Let’s update one of our employee entries. In order to update our entries (i.e. PUT request), we will need to create the update class within the app level schema file.

One of the most important aspects of updating an entry is the use of the global ID. You may have noticed from our original queries that graphene returns a different ID value (e.g. Q2l0eU5vZGU6NA==) than the primary key that we usually expect from the django backend. This is a node ID assigned by the graphene server to use to identify this particular node.

In order to update any of our entries, we first need to get the unique node ID (plus any other model value) and then use it to update the node values. The node ID is very important because it guarantees uniqueness when updating or deleting an entry.

Let’s start by creating our update class

20). First thing we need to do is import the global id module from graphene relay which we will use to “retrieve the node id”

Add the snippet below to the top of the app level schema file

from graphql_relay.node.node import from_global_id #for updating

Then add the update class

class UpdateEmployee(graphene.relay.ClientIDMutation):
    employee = graphene.Field(EmployeeNode)
    class Input:
        id = graphene.String()
        employee_name = graphene.String()
        employee_city = graphene.String()
        employee_title = graphene.String()
    def mutate_and_get_payload(root, info, **input):
        employee = Employee.objects.get(
            pk=from_global_id(input.get(‘id’))[1])
        employee.employee_name = input.get(‘employee_name’)
        employee.employee_city = City.objects.get(
            city_name=input.get(‘employee_city’))
        employee.employee_title = Title.objects.get(
            title_name=input.get(‘employee_title’))
        employee.save()
        return UpdateEmployee(employee=employee)

Add the update class to the mutation class

class Mutation(graphene.AbstractType):
    create_title = CreateTitle.Field()
    create_employee = CreateEmployee.Field()
    update_employee = UpdateEmployee.Field()

Let’s go back to the graphiql IDE and find the employee entry that we just created. Let’s assume that John Phillips decided to change his first name to Jason

This is image title

You will notice that even though we are updating only the employee name, we had to also provide all field values for the Employee model. If we want to avoid that and update only the column that we want, then we will have to update the “mutate_and_get_upload” function and delete (or comment out) the model fields that we do not want to be overridden

class UpdateEmployee(graphene.relay.ClientIDMutation):
    employee = graphene.Field(EmployeeNode)
    class Input:
        id = graphene.String()
        employee_name = graphene.String()
        employee_city = graphene.String()
        employee_title = graphene.String()
    def mutate_and_get_payload(root, info, **input):
        employee = Employee.objects.get(
            pk=from_global_id(input.get(‘id’))[1])
        employee.employee_name = input.get(‘employee_name’)
        # employee.employee_city = City.objects.get(
        #     city_name=input.get(‘employee_city’))
        # employee.employee_title = Title.objects.get(
        #     title_name=input.get(‘employee_title’))
        employee.save()
        return UpdateEmployee(employee=employee)

Now we can go back to the IDE and update only the name

This is image title

mutation {
   updateEmployee(input: {id: “RW1wbG95ZWVOb2RlOjM=”, employeeName: “Jason Phillips”}) {
    employee {
      employeeName
     }
   }
}

This is image title

Deleting Entries

Now we will focus on deleting previously created entries. Very similar to updating entries, the node ID is very important when deleting an entry because of its uniqueness.

21). Let’s start by creating a delete class

class DeleteEmployee(graphene.relay.ClientIDMutation):
    employee = graphene.Field(EmployeeNode)
    class Input:
        id = graphene.String()
    def mutate_and_get_payload(root, info, **input):
        employee = Employee.objects.get(
            pk=from_global_id(input.get(‘id’))[1])
        employee.delete()
        return DeleteEmployee(employee=employee)

Update the mutation class too

class Mutation(graphene.AbstractType):
    create_title = CreateTitle.Field()
    create_employee = CreateEmployee.Field()
    update_employee = UpdateEmployee.Field()
    delete_employee = DeleteEmployee.Field()

Let’s go back to the graphiql IDE. In this scenario, let’s assume that our newly updated Jason Phillips decided the company is not a good fit for him and decided to quit. Now we need to delete him from the system

This is image title

ACCESSING THE GRAPHENE SERVER FROM OUTSIDE GRAPHIQL

So far, you will notice that we have been running all our graphql queries from the graphiql IDE which is tightly coupled with django. There might be some situations where this may not be the optimal solution i.e. exposing your API to your customers.

In this example, I attempt to use insomia to make my requests but you can use any REST client like postman.

22). In this example, I’ll attempt to run a query to return all the titles using the localhost (http://127.0.0.1:8000/graphql/) as the endpoint

This is image title

I got a 403 Forbidden error as well as a django error mentioning that we got a CSRF verification failure

There is a very easy solution for this.

23). Navigate to our project level urls.py file. Add this snippet to the top of the file

from django.views.decorators.csrf import csrf_exempt

Now wrap the GraphQL view within the url patterns with “csrf_exempt”

urlpatterns = [
 path(‘admin/’, admin.site.urls),
 path(‘graphql/’, csrf_exempt(GraphQLView.as_view(graphiql=True))), 
]

Now go back to the REST client and run the query again. It should work now!

This is image title

Thank you very much for taking the time to read through this. If you want to see the repo for this project, you can click here

You can check out part 2 of this tutorial which shows the steps to build and view all of this data within a VueJS application.

Recommended Reading

Server-Side Rendering (SSR) Vue.js Application to Cloudflare Workers

Statistical look over Vue.js, React and Angular

Top Vue.js Developers in USA

Good luck!