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

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

  • 471

Create a Fullstack app with Django, GraphQL and VueJS (Part 2). In the first part of this tutorial, we created a fictional employee management system using Django and Graphene which allowed us to see all employees,

In the first part of this tutorial, we created a fictional employee management system using Django and Graphene which allowed us to see all employees, create new entries as well as updating and deleting existing entries.

In this second part, we will continue from where we left off and build a simple VueJS application which we can use to view our employees and filter by cities.

Prerequisites

In order to start working with vueJS and other front end tools, one of the first things we need is a runtime. The most popular javascript runtime is NodeJS which we need for this project.

So head on to the NodeJS website and download the version for your machine. Once the installation is completed, open your terminal and confirm that it exists. Type node -v and you should get a response with a version number

$ node -v
v11.10.0

NodeJS comes packaged with npm which is a javascript package manager which essentially makes it easy for developers to download and install needed packages (similar to pip in python). You can also confirm that it is installed by typing the command below

$ npm -v
6.7.0

Lastly, we want to install vueJS and vue-cli. This is where the advantage of npm comes in

sudo npm install -g @vue/cli

Adding sudo to your vueJS install statement is not mandatory but you may get an error in some cases, especially if you are not logged in as an admin on your computer. Once that download is complete, you can confirm the installation by just typing “Vue” in your terminal

$ vue
Usage: vue <command> [options]
Options:
 -V, — version output the version number
 -h, — help output usage information
Commands:
 create [options] <app-name> create a new project powered by vue-cli-service
 add [options] <plugin> [pluginOptions] install a plugin and invoke its generator in an already created project
 invoke [options] <plugin> [pluginOptions] invoke the generator of a plugin in an already created project
 inspect [options] [paths…] inspect the webpack config in a project with vue-cli-service
 serve [options] [entry] serve a .js or .vue file in development mode with zero config
 build [options] [entry] build a .js or .vue file in production mode with zero config
 ui [options] start and open the vue-cli ui
 init [options] <template> <app-name> generate a project from a remote template (legacy API, requires @vue/cli-init)
 config [options] [value] inspect and modify the config
 upgrade [semverLevel] upgrade vue cli service / plugins (default semverLevel: minor)
 info print debugging information about your environment
 Run vue <command> — help for detailed usage of given command.

Now we’re ready to begin

1). Navigate to a location on your local drive where you want to create your project and create a folder for your application. In my case, I’m going to my desktop and creating a directory called “vueapps” and changing into the directory

$ cd ~/Desktop
$ mkdir vueapps && cd vueapps

I will now create a new vueJS app called “company”

$ sudo vue init webpack company

You will go through a number of prompts similar to the image below. Select your preferences as required.

This is image title

After the installation is completed, you should now see a directory with the same name as your project. When you change into the directory, you should see the following files and directories

$ cd company

This is image title

2). The next step will be to install all of the plugins that were bundled with vueJS and vue-cli. If you open the “package.json” file, you will see a list of all the plugins and dependencies

This is image title

To install all of these dependencies, run “npm install”. Make sure that you are inside the company app directory

$ sudo npm install

Just a side note that all of the plugins will be installed within the node_modules subdirectory. If everything runs fine, you should see a message similar to below (may be more or less packages depending on your system)

audited 11689 packages in 5.466s
found 0 vulnerabilities

Now we can create a development server to start working on our application within the local computer

$ npm run dev
I Your application is running here: http://localhost:8080

You can now navigate to the localhost IP provided and you should see a screen similar to the image below

This is image title

Now, we are ready to start building our application

3). If you are familiar with Vue, you will know that Vue is akin to a single page application (SPA) which is made up of multiple components and routes.

Majority of our code will exist in the “src” directory where all of the components for our application will reside in the “components” subdirectory.

This is image title

The demo page (http://localhost:8080) that we navigated to after our installation is a product of the HelloWorld.vue file (component). Each component is made up of a template tag, a script tag and a style tag.

However, the App.vue file is parent SPA file for the application where all components and routers need to be registered in order for them to be displayed within the browser. Even though all of the data that is displayed in the browser is created and exists in the HelloWorld.vue file, it needs to be registered in the App.Vue file or else it will not be displayed

Now we will need to create a new component to visualize our company data. Make sure that you are within the src/components directory and create a new component

$ cd src/components
$ sudo touch Comp.vue

4). Open up the new Comp.vue file and start with a simple component where the root div in the template simply prints out “Hello World”, an empty function and no style.

We also need to add a selector (CompanyData) within the script tag and register it in the App.vue file in order for the component to work.

<template>

  <div>

    <p>Hello World</p>

  </div>

</template>

<script>
  export default{
    name: 'CompanyData',
    data(){
      return {}
    }
  }
</script>

<style scoped>
</style>

Comp.vue

5). Now, we have to go to the App.Vue file to register this new component. Notice the new line additions (lines 5, 11 & 17)

<template>
  <div id="app">
    <img src="./assets/logo.png">
    <HelloWorld/>
    <CompanyData/> <!-- Added New tag -->
  </div>
</template>

<script>
import HelloWorld from './components/HelloWorld'
import CompanyData from './components/Comp' //import new component
export default {
  name: 'App',
  components: {
    HelloWorld,
    CompanyData //register new component
  }
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

App.vue

Let’s view our changes by running the dev server again and navigate to the browser

$ npm run dev

This is image title

You should now see your “Hello World” component at the bottom of the page. Success!

Now we can start working to organize and visualize the data from our django application.

6). For our company application, we want to be able to list all of the current employees within the company as well as having the ability to filter by city. In order to make this visually appealing and easily readable, we’ll use a bootstrap table to display all the pertinent data.

First of all, we will install bootstrap

$ npm install bootstrap

This is image title

7). I won’t dive too much into the styling aspects since this is not a webpage design tutorial and I assume that if you are reading this, you have a pretty good grasp of CSS.

For my page:

  • I imported the bootstrap stylesheet from the node_modules directory into the style block at the bottom of the App.vue file (line 57)
  • Commented out the App.vue default styles (lines 59–64)
  • Added a navbar which I copied from the bootstrap site (lines 3–37)
  • Commented out the default HelloWorld component (line 38)
<template>
  <div id="app">
  <nav class="navbar navbar-expand-lg navbar-light bg-light">
    <a class="navbar-brand" href="#">Navbar</a>
    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>

    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav mr-auto">
        <li class="nav-item active">
          <a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="#">Link</a>
        </li>
        <li class="nav-item dropdown">
          <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
            Dropdown
          </a>
          <div class="dropdown-menu" aria-labelledby="navbarDropdown">
            <a class="dropdown-item" href="#">Action</a>
            <a class="dropdown-item" href="#">Another action</a>
            <div class="dropdown-divider"></div>
            <a class="dropdown-item" href="#">Something else here</a>
          </div>
        </li>
        <li class="nav-item">
          <a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
        </li>
      </ul>
      <form class="form-inline my-2 my-lg-0">
        <input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search">
        <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
      </form>
    </div>
  </nav>
    <!-- <HelloWorld/> -->
    <CompanyData/> <!-- Added New tag -->
  </div>
</template>

<script>
import HelloWorld from './components/HelloWorld'
import CompanyData from './components/Comp' //import new component
export default {
  name: 'App',
  components: {
    HelloWorld,
    CompanyData //register new component
  }
}
</script>

<style>
@import "../node_modules/bootstrap/dist/css/bootstrap.min.css"; /*Importing Bootstrap*/
#app {
/*  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;*/
}
</style>

App.vue

You can now see that all that is left on the page is the new navbar and the our “Hello World” comment

This is image title

Hope your page looks similar to mine.

We finally get to the fun stuff! We get to connect our Vue application to the GraphQL API.

8). We will need to install axios in order to make HTTP requests from our application.

npm install axios

In order to continue, you have to make sure that your django application from part 1 of this tutorial is running. If not, navigate to the directory and run python manage.py runserver. Navigate to http://127.0.0.1:8000/graphql to confirm that Django app is running

9). Let’s make some edits to the Comp.vue file to be able to GET data from the API.

  • Import axios into the Comp.vue file within the script tag (line 13)
  • Add an async mounted lifecycle hook (line 21)
  • Add a data function to collect data (lines 16-20)
  • Create a POST method to query the GraphQL API (line 24)
  • Use a simple GraphQL query to return data (AllTitles) (lines 27–39)
  • Add a directory container within the template tag to return results/data to the page (line 18)
<template>

  <div>

    <p>Hello World</p>
    {{ directory }}

  </div>

</template>

<script>
  import axios from 'axios'
  export default{
  	name: 'CompanyData',
    data(){
      return {
      	directory: []
      }
    },
    async mounted () {
    	try {
    		var result = await axios({
    			method: 'POST',
    			url: 'http://127.0.0.1:8000/graphql/',
    			data: {
    				query: `
    				{
					  allTitles {
					    edges {
					      node {
					        id
					        titleName
					      }
					    }
					  }
					}
    				`
    			}
    		})
    		this.directory = result.data.data.allTitles
    	} catch (error) {
    		console.error(error)
    	}
    }
  }
</script>

<style scoped>
</style>

Comp.vue

Save and refresh your Vue app now.

10). Let’s see if we can see the returned data in the browser console now. Open “inspect element” on your browser and go to the console tab.

This is image title

If you see an error mentioning that your request has been blocked by a CORS policy, don’t be alarmed. I actually did that on purpose.

If you’ve worked on any application which requires you to make HTTP requests, you’ve most likely hit a CORS error at least once in your lifetime. This is usually the case when a web application makes a request from a resource that has a different origin (domain, protocol, port) from its own. In our case, even though both applications are running on localhost, they are running on two different ports.

11). In order to solve this, we have to make some slight changes to our django application. First of all, we need to install django-cors-headers

pipenv install django-cors-headers

Add “corsheaders” to the list of installed apps in the settings.py file

INSTALLED_APPS = [
 ‘django.contrib.admin’,
 ‘django.contrib.auth’,
 ‘django.contrib.contenttypes’,
 ‘django.contrib.sessions’,
 ‘django.contrib.messages’,
 ‘django.contrib.staticfiles’,
 ‘graphene_django’,
 ‘company’,
 ‘corsheaders’, #added for cors
]

Add “corsheaders.middleware.CorsMiddleware” to the middleware list in the settings.py file

MIDDLEWARE = [
 ‘corsheaders.middleware.CorsMiddleware’, #added for cors
 ‘django.middleware.security.SecurityMiddleware’,
 ‘django.contrib.sessions.middleware.SessionMiddleware’,
 ‘django.middleware.common.CommonMiddleware’,
 ‘django.middleware.csrf.CsrfViewMiddleware’,
 ‘django.contrib.auth.middleware.AuthenticationMiddleware’,
 ‘django.contrib.messages.middleware.MessageMiddleware’,
 ‘django.middleware.clickjacking.XFrameOptionsMiddleware’,
]

Lastly, add a whitelist of all inbound urls that will be making requests to the GraphQL server to the bottom of the settings.py file

CORS_ORIGIN_WHITELIST = (
 ‘localhost:8080/’
 )

Now, let’s restart your Django app && restart your Vue app and try again.

This is image title

The error should now be gone and now we can see data returned (even though it does not look pretty).

12). In order to make the data look more presentable, I’m going to add a bootstrap table to display the data. I’m also going to use this opportunity to change the GraphQL query to return all employees in the company (AllEmployees)

  • I changed the query from AllTitles to AllEmployees (lines 40–55)
  • Added a bootstrap table and created a for-loop within the table tag to return the data in rows (lines 4–19)
<template>

  <div>
    <table class="table table-striped mt-4">
      <thead>
      <tr>
        <th scope="col">name</th>
        <th scope="col">title</th>
        <th scope="col">city</th>
      </tr>
      </thead>
      <tbody>
      <tr v-for="input in directory.edges" :key="input.id">
        <td>{{ input.node.employeeName }}</td>
        <td>{{ input.node.employeeTitle.titleName }}</td>
        <td>{{ input.node.employeeCity.cityName }}</td>
      </tr>
      </tbody>
    </table>
  </div>

</template>

<script>
  import axios from 'axios'
  export default{
  	name: 'CompanyData',
    data(){
      return {
      	directory: []
      }
    },
    async mounted () {
    	try {
	var result = await axios({
		method: 'POST',
		url: 'http://127.0.0.1:8000/graphql/',
		data: {
			query: `
			{
			  allEmployees {
			    edges {
			      node {
				id
				employeeName
				employeeTitle {
				  titleName
				}
				employeeCity {
				  cityName
				}
			      }
			    }
			  }
			}
			`
    			}
    		})
    		this.directory = result.data.data.allEmployees
    	} catch (error) {
    		console.error(error)
    	}
    }
  }
</script>

<style scoped>
</style>

Comp.vue

This is image title
Now this looks a lot better and more presentable!!

While this looks great. This is only returning data and is not making great usage of our GraphQL server.

13). Let’s add a search function to our app to allow users to be able to search for employees using the city.

  • I added a search form (lines 4–9) with a “city” v-model
  • Added a city variable to the data function to hold the value entered into the form (line 36)
  • Wrapped the async mounted function with a method (line 40)
  • Added a query filter and the city variable to the GraphQL query (line 49)
<template>

  <div>
    <form action="" @submit.prevent="mounted()">
      <div class="col-sm-6">
        <input v-model="city" city="" type="text" class="form-control">
        <button type="submit" name="button">Submit</button>
      </div>
    </form>   
    <table class="table table-striped mt-4">
      <thead>
      <tr>
        <th scope="col">name</th>
        <th scope="col">title</th>
        <th scope="col">city</th>
      </tr>
      </thead>
      <tbody>
      <tr v-for="input in directory.edges" :key="input.id">
        <td>{{ input.node.employeeName }}</td>
        <td>{{ input.node.employeeTitle.titleName }}</td>
        <td>{{ input.node.employeeCity.cityName }}</td>
      </tr>
      </tbody>
    </table>
  </div>

</template>

<script>
  import axios from 'axios'
  export default{
    name: 'CompanyData',
    data(){
      return {
        city: '',
        directory: []
      }
    },
    methods: {
    async mounted () {
	try {
        var result = await axios({
          method: 'POST',
          url: 'http://127.0.0.1:8000/graphql/',
          data: {
            query: `
            {
              allEmployees(employeeCity_CityName: "`+this.city+`") {
                edges {
                  node {
                    id
                    employeeName
                    employeeTitle {
                      titleName
                    }
                    employeeCity {
                      cityName
                    }
                  }
                }
              }
            }
            `
          }
        })
        this.directory = result.data.data.allEmployees
      } catch (error) {
        console.error(error)
      }
    }
  }
}
</script>

<style scoped>
</style>

Comp.vue

This is image title
You should now be able to see a search box at the top of the page but you will also notice that the table is not returning any data anymore. This is because it now requires you enter a city name for it to return results.

I’ll enter “New York” in the search box and hit ENTER

This is image title

Same for “Miami”

This is image title

Looking great now!!

You should now be able to add more data to the django server and retrieve it from the VueJS frontend.

Thank you for taking the time to read through the two parts of this tutorial and apologies for the long wait in completing the series. If you want to see the repo for this project, you can click here

Recommended Reading

5 ways to use Vue DevTools for Quicker and more Efficient Debugging

Vue.js Testing Guide: Unit testing in Vue and Mocha

How to integrate the Smart Chart component in Vue.js Application

Good luck!