Are you a regular Django user? Do you find yourself wanting to decouple your back end and front end? Do you want to handle data persistence in the API while displaying the data in a single-page app (SPA) in the browser using a client-side framework like React or Vue? Youβre in luck. This tutorial will take you through the process of building a Django blog back end and a Vue front end, using GraphQL to communicate between them.
Projects are an effective way to learn and solidify concepts. This tutorial is structured as a step-by-step project so you can learn in a hands-on way and take breaks as needed.
In this tutorial, youβll learn how to:
- Translate your Django models into a GraphQL API
- Run the Django server and a Vue application on your computer at the same time
- Administer your blog posts in the Django admin
- Consume a GraphQL API in Vue to show data in the browser
You can download all the source code youβll use to build your Django blog application by clicking the link below:
Get the Source Code: Click here to get the source code youβll use to build a blog application with Django, Vue, and GraphQL in this tutorial.
Demo: A Django Blog Admin, a GraphQL API, and a Vue Front End
Blog applications are a common starter project because they involve create, read, update, and delete (CRUD) operations. In this project, youβll use the Django admin to do the heavy CRUD lifting and focus on providing a GraphQL API for your blog data.
Hereβs a demonstration of the completed project in action:
Next, youβll make sure you have all the necessary background information and tools before diving into building your blog application.
Project Overview
Youβll create a small blogging application with some rudimentary features. Authors can write many posts. Posts can have many tags and can be either published or unpublished.
Youβll build the back end of this blog in Django, complete with an admin for adding new blog content. Then youβll expose the content data as a GraphQL API and use Vue to display that data in the browser. Youβll accomplish this in several high-level steps:
- Set up the Django blog
- Create the Django blog admin
- Set up Graphene-Django
- Set up
django-cors-headers - Set up Vue.js
- Set up Vue Router
- Create the Vue Components
- Fetch the data
Each section will provide links to any necessary resources and give you a chance to pause and come back as needed.
Prerequisites
Youβll be best equipped for this tutorial if you already have a solid foundation in some web application concepts. You should understand how HTTP requests and responses and APIs work. You can check out Python & APIs: A Winning Combo for Reading Public Data to understand the details of using GraphQL APIs vs REST APIs.
Because youβll use Django to build the back end for your blog, youβll want to be familiar with starting a Django project and customizing the Django admin. If you havenβt used Django much before, you might also want to try building another Django-only project first. For a good introduction, check out Get Started with Django Part 1: Build a Portfolio App.
Because youβll be using Vue on the front end, some experience with reactive JavaScript will also help. If youβve only used a DOM manipulation paradigm with a framework like jQuery in the past, the Vue introduction is a good foundation.
Familiarity with JSON is also important because GraphQL queries are JSON-like and return data in JSON format. You can read about Working with JSON Data in Python for an introduction. Youβll also need to install Node.js to work on the front end later in this tutorial.
Step 1: Set Up the Django Blog
Before going too far, youβll need a directory in which you can organize the code for your project. Start by creating one called dvg/, short for Django-Vue-GraphQL:
$ mkdir dvg/
$ cd dvg/
Youβll also be completely splitting up the front-end and back-end code, so itβs a good idea to start creating that separation right off the bat. Create a backend/ directory in your project directory:
$ mkdir backend/
$ cd backend/
Youβll put your Django code this directory, completely isolated from the Vue code youβll create later in this tutorial.
Install Django
Now youβre ready to start building the Django application. To separate the dependencies for this project from your other projects, create a virtual environment in which youβll install your projectβs requirements. You can read more about virtual environments in Python Virtual Environments: A Primer. The rest of the tutorial assumes you will run commands related to Python and Django within your active virtual environment.
Now that you have a virtual environment in which to install requirements, create a requirements.txt file in the backend/ directory and define the first requirement youβll need:
Django==3.1.7
Once youβve saved the requirements.txt file, use it to install Django:
(venv) $ python -m pip install -r requirements.txt
Now youβll be able to start creating your Django project.
Create the Django Project
Now that Django is installed, use the django-admin command to initialize your Django project:
(venv) $ django-admin startproject backend .
This creates a manage.py module and a backend package in the backend/ directory, so your project directory structure should now look like this:
dvg
βββ backend
βββ manage.py
βββ requirements.txt
βββ backend
βββ __init__.py
βββ asgi.py
βββ settings.py
βββ urls.py
βββ wsgi.py
This tutorial wonβt cover or need all these files, but it wonβt hurt for them to be present.
Run Django Migrations
Before adding anything specific to your application, you should also run Djangoβs initial migrations. If you havenβt dealt with migrations before, then check out Django Migrations: A Primer. Run the migrations using the migrate management command:
(venv) $ python manage.py migrate
You should see a long list of migrations, each with an OK after it:
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying admin.0003_logentry_add_action_flag_choices... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying auth.0010_alter_group_name_max_length... OK
Applying auth.0011_update_proxy_permissions... OK
Applying auth.0012_alter_user_first_name_max_length... OK
Applying sessions.0001_initial... OK
This will create an SQLite database file called db.sqlite3 that will also store the rest of the data for your project.
Create a Superuser
Now that you have the database, you can create a superuser. Youβll need this user so you can eventually log in to the Django admin interface. Use the createsuperuser management command to create one:
(venv) $ python manage.py createsuperuser
Youβll be able to use the username and password you supplied in this step to log in to the Django admin in the next section.
Step 1 Summary
Now that youβve installed Django, created the Django project, run the Django migrations, and created a superuser, you have a fully functioning Django application. You should now be able to start the Django development server and view it in your browser. Start the server using the runserver management command, which will listen to port 8000 by default:
(venv) $ python manage.py runserver
Now visit http://localhost:8000 in your browser. You should see the Django splash page, indicating that the installation worked successfully. You should also be able to visit http://localhost:8000/admin, where youβll see a login form.
Use the username and password you made for your superuser to log into the Django admin. If everythingβs working, then youβll be taken to the Django admin dashboard page. This page will be pretty bare for the moment, but youβll make it much more interesting in the next step.
Step 2: Create the Django Blog Admin
Now that you have the foundations of your Django project in place, youβre ready to start creating some of the core business logic for your blog. In this step youβll create the data models and the administrative configuration for authoring and managing blog content.
Create the Django Blog Application
Keep in mind that a single Django project can contain many Django applications. You should separate your blog-specific behavior into its own Django application so that it remains distinct from any future applications you build into your project. Create the application using the startapp management command:
(venv) $ python manage.py startapp blog
This will create a blog/ directory with several skeleton files:
blog
βββ __init__.py
βββ admin.py
βββ apps.py
βββ migrations
β βββ __init__.py
βββ models.py
βββ tests.py
βββ views.py
Youβll make changes and additions to some of these files later in this tutorial.
Enable the Django Blog Application
Creating a Django application doesnβt make it available in your project by default. To make sure the project knows about your new blog application, youβll need to add it to the list of installed applications. Update the INSTALLED_APPS variable in backend/settings.py:
INSTALLED_APPS = [
...
"blog",
]
This will help Django discover information about your application, such as the data models and URL patterns it contains.
Create the Django Blog Data Models
Now that Django can discover your blog application, you can create the data models. Youβll create three models to start:
Profilestores additional information about blog users.Tagrepresents a category in which blog posts can be grouped.Poststores the content and metadata of each blog post.
Youβll add each of these models to blog/models.py. First, import Djangoβs django.db.models module:
from django.db import models
Each of your models will inherit from the models.Model class.
The Profile Model
The Profile model will have a few fields:
useris a one-to-one association to the Django user with which the profile is associated.websiteis an optional URL where you can learn more about the user.biois an optional, tweet-sized blurb to learn more about the user quickly.
Youβll first need to import the settings module from Django:
from django.conf import settings
Then create the Profile model, which should look like the following snippet:
class Profile(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.PROTECT,
)
website = models.URLField(blank=True)
bio = models.CharField(max_length=240, blank=True)
def __str__(self):
return self.user.get_username()
The __str__ method will make the Profile objects you create appear in a more human-friendly manner on the admin site.
The Tag Model
The Tag model has just one field, name, which stores a short, unique name for the tag. Create the Tag model, which should look like the following snippet:
class Tag(models.Model):
name = models.CharField(max_length=50, unique=True)
def __str__(self):
return self.name
Again, __str__ will make the Tag objects you create appear in a more human-friendly manner on the admin site.
The Post Model
The Post model, as you might imagine, is the most involved. It will have several fields:
| Field name | Purpose |
|---|---|
title |
The unique title of the post to display to readers |
subtitle |
An optional clarifier of the postβs content to help readers understand if they want to read it |
slug |
A unique, readable identifier for the post to use in URLs |
body |
The postβs content |
meta_description |
An optional description to use for search engines like Google |
date_created |
A timestamp of the postβs creation |
date_modified |
A timestamp of the postβs most recent edit |
publish_date |
An optional timestamp when the post goes live |
published |
Whether the post is currently available to readers |
author |
A reference to the user profile who wrote the post |
tags |
The list of tags associated with the post, if any |
Because blogs usually show the most recent posts first, youβll also want the ordering to be by published date, with the most recent first. Create the Post model, which should look like the following snippet:
class Post(models.Model):
class Meta:
ordering = ["-publish_date"]
title = models.CharField(max_length=255, unique=True)
subtitle = models.CharField(max_length=255, blank=True)
slug = models.SlugField(max_length=255, unique=True)
body = models.TextField()
meta_description = models.CharField(max_length=150, blank=True)
date_created = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
publish_date = models.DateTimeField(blank=True, null=True)
published = models.BooleanField(default=False)
author = models.ForeignKey(Profile, on_delete=models.PROTECT)
tags = models.ManyToManyField(Tag, blank=True)
The on_delete=models.PROTECT argument for author ensures that you wonβt accidentally delete an author who still has posts on the blog. The ManyToManyField relationship to Tag allows you to associate a post with zero or more tags. Each tag can be associated to many posts.
Create the Model Admin Configuration
Now that your models are in place, youβll need to tell Django how they should be displayed in the admin interface. In blog/admin.py, start by importing Djangoβs admin module and your models:
from django.contrib import admin
from blog.models import Profile, Post, Tag
Then create and register the admin classes for Profile and Tag, which only need the model specified:
@admin.register(Profile)
class ProfileAdmin(admin.ModelAdmin):
model = Profile
@admin.register(Tag)
class TagAdmin(admin.ModelAdmin):
model = Tag
Just like the model, the admin class for Post is more involved. Posts contain a lot of information, so it helps to be more judicious about what information you display to avoid crowding the interface.
In the list of all posts, youβll specify that Django should only show the following information about each post:
- ID
- Title
- Subtitle
- Slug
- Publish date
- Publish status
To make browsing and editing posts more fluid, youβll also tell the Django admin system to take the following actions:
- Allow filtering the post list by posts that are published or unpublished.
- Allow filtering posts by publish date.
- Allow editing all the displayed fields, with the exception of the ID.
- Allow searching for posts using the title, subtitle, slug, and body.
- Prepopulate the slug field using the title and subtitle fields.
- Use the publish date of all posts to create a browsable date hierarchy.
- Show a button at the top of the list to save changes.
Create and register the PostAdmin class:
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
model = Post
list_display = (
"id",
"title",
"subtitle",
"slug",
"publish_date",
"published",
)
list_filter = (
"published",
"publish_date",
)
list_editable = (
"title",
"subtitle",
"slug",
"publish_date",
"published",
)
search_fields = (
"title",
"subtitle",
"slug",
"body",
)
prepopulated_fields = {
"slug": (
"title",
"subtitle",
)
}
date_hierarchy = "publish_date"
save_on_top = True
You can read more about all the options the Django admin has to offer in Customize the Django Admin With Python.
Create the Model Migrations
Django has all the information it needs to administer and persist your blog content, but youβll first need to update the database to support these changes. Earlier in this tutorial, you ran Djangoβs migrations for its built-in models. Now, youβll create and run migrations for your models.
First, create the migrations using the makemigrations management command:
(venv) $ python manage.py makemigrations
Migrations for 'blog':
blog/migrations/0001_initial.py
- Create model Tag
- Create model Profile
- Create model Post
This creates a migration whose name is 0001_initial.py by default. Run this migration using the migrate management command:
(venv) $ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, blog, contenttypes, sessions
Running migrations:
Applying blog.0001_initial... OK
Note that the migrations should say OK after the migration name.
Step 2 Summary
You now have all your data models in place, and youβve configured the Django admin so that you can add and edit those models.
Start or restart the Django development server, visit the admin interface at http://localhost:8000/admin, and explore whatβs changed. You should see links to the list of tags, profiles, and posts and links to add or edit each of them. Try adding and editing a few of each to see how the admin interface responds.
Step 3: Set Up Graphene-Django
At this point, youβve completed enough of the back end that you could decide to go headlong in the Django direction. You could use Djangoβs URL routing and templating engine to build pages that would show all the post content you create in the admin to readers. Instead, youβll wrap the back end youβve created in a GraphQL API so you can eventually consume it from the browser and provide a richer client-side experience.
GraphQL allows you to retrieve only the data you need, which can be useful compared to very large responses that are common in RESTful APIs. GraphQL also provides more flexibility about projecting data, so you can often retrieve data in new ways without changing the logic of the service providing the GraphQL API.
Youβll use Graphene-Django to integrate what youβve created so far into a GraphQL API.
Install Graphene-Django
To get started with Graphene-Django, first add it to your projectβs requirements file:
graphene-django==2.14.0
Then install it using the updated requirements file:
(venv) $ python -m pip install -r requirements.txt
Add "graphene_django" to the INSTALLED_APPS variable in your projectβs settings.py module so Django will find it:
INSTALLED_APPS = [
...
"blog",
"graphene_django",
]
Graphene-Django is now installed and ready to be configured.
Configure Graphene-Django
To get Graphene-Django working in your project, youβll need to configure a few pieces:
- Update
settings.pyso the project knows where to look for GraphQL information. - Add a URL pattern to serve the GraphQL API and GraphiQL, GraphQLβs explorable interface.
- Create the GraphQL schema so Graphene-Django knows how to translate your models into GraphQL.
Update Django Settings
The GRAPHENE setting configures Graphene-Django to look in a particular place for your GraphQL schema. Point it to the blog.schema.schema Python path, which youβll create shortly:
GRAPHENE = {
"SCHEMA": "blog.schema.schema",
}
Note that this addition may cause Django to produce an import error, which youβll resolve when you create your GraphQL schema.
Add a URL Pattern for GraphQL and GraphiQL
To let Django serve the GraphQL endpoint and the GraphiQL interface, youβll add a new URL pattern to backend/urls.py. Youβll point the URL at Graphene-Djangoβs GraphQLView. Because youβre not using the Django template engineβs cross-site request forgery (CSRF) protection features, youβll also need to import Djangoβs csrf_exempt decorator to mark the view as exempt from CSRF protection:
from django.views.decorators.csrf import csrf_exempt
from graphene_django.views import GraphQLView
Then, add the new URL pattern to the urlpatterns variable:
urlpatterns = [
...
path("graphql", csrf_exempt(GraphQLView.as_view(graphiql=True))),
]
The graphiql=True argument tells Graphene-Django to make the GraphiQL interface available.
Create the GraphQL Schema
Now youβll create the GraphQL schema, which should feel similar to the admin configuration you created earlier. The schema consists of several classes that are each associated with a particular Django model and one to specify how to resolve a few important types of queries youβll need in the front end.
Create a new schema.py module in the blog/ directory. Import Graphene-Djangoβs DjangoObjectType, your blog models, and the Django User model:
from django.contrib.auth import get_user_model
from graphene_django import DjangoObjectType
from blog import models
Create a corresponding class for each of your models and the User model. They should each have a name that ends with Type because each one represents a GraphQL type. Your classes should look like the following:
class UserType(DjangoObjectType):
class Meta:
model = get_user_model()
class AuthorType(DjangoObjectType):
class Meta:
model = models.Profile
class PostType(DjangoObjectType):
class Meta:
model = models.Post
class TagType(DjangoObjectType):
class Meta:
model = models.Tag
Youβll need to create a Query class that inherits from graphene.ObjectType. This class will bring together all the type classes you created, and youβll add methods to it to indicate the ways in which your models can be queried. Youβll need to import graphene first:
import graphene
The Query class is made up of a number of attributes that are either graphene.List or graphene.Field. Youβll use graphene.Field if the query should return a single item and graphene.List if it will return multiple items.
For each of these attributes, youβll also create a method to resolve the query. You resolve a query by taking the information supplied in the query and returning the appropriate Django queryset in response.
The method for each resolver must start with resolve_, and the rest of the name should match the corresponding attribute. As an example, the method to resolve the queryset for the all_posts attribute must be named resolve_all_posts.
Youβll create queries to get:
- All the posts
- An author with a given username
- A post with a given slug
- All posts by a given author
- All posts with a given tag
Create the Query class now. It should look like the following snippet:
class Query(graphene.ObjectType):
all_posts = graphene.List(PostType)
author_by_username = graphene.Field(AuthorType, username=graphene.String())
post_by_slug = graphene.Field(PostType, slug=graphene.String())
posts_by_author = graphene.List(PostType, username=graphene.String())
posts_by_tag = graphene.List(PostType, tag=graphene.String())
def resolve_all_posts(root, info):
return (
models.Post.objects.prefetch_related("tags")
.select_related("author")
.all()
)
def resolve_author_by_username(root, info, username):
return models.Profile.objects.select_related("user").get(
user__username=username
)
def resolve_post_by_slug(root, info, slug):
return (
models.Post.objects.prefetch_related("tags")
.select_related("author")
.get(slug=slug)
)
def resolve_posts_by_author(root, info, username):
return (
models.Post.objects.prefetch_related("tags")
.select_related("author")
.filter(author__user__username=username)
)
def resolve_posts_by_tag(root, info, tag):
return (
models.Post.objects.prefetch_related("tags")
.select_related("author")
.filter(tags__name__iexact=tag)
)
You now have all the types and resolvers for your schema, but remember that the GRAPHENE variable you created points to blog.schema.schema. Create a schema variable that wraps your Query class in graphene.Schema to tie it all together:
schema = graphene.Schema(query=Query)
This variable matches the "blog.schema.schema" value you configured for Graphene-Django earlier in this tutorial.
Step 3 Summary
Youβve fleshed out your blogβs data model, and now youβve also wrapped your data model with Graphene-Django to serve that data as a GraphQL API.
Run the Django development server and visit http://localhost:8000/graphql. You should see the GraphiQL interface with some commented text that explains how to use the tool.
Expand the Docs section in the top right of the screen and click query: Query. You should see each of the queries and types that you configured in your schema.
If you havenβt created any test blog content yet, do so now. Try the following query, which should return a list of all the posts youβve created:
{
allPosts {
title
subtitle
author {
user {
username
}
}
tags {
name
}
}
}
The response should return a list of posts. The structure of each post should match the shape of the query, like the following example:
{
"data": {
"allPosts": [
{
"title": "The Great Coney Island Debate",
"subtitle": "American or Lafayette?",
"author": {
"user": {
"username": "coney15land"
}
},
"tags": [
{
"name": "food"
},
{
"name": "coney island"
}
]
}
]
}
}
If youβve saved some posts and you see them in the response, then youβre ready to continue on.
Step 4: Set Up django-cors-headers
Youβll need to take one more step before you can call the back-end work complete. Because the back end and the front end will run on different ports locally, and because they might run on entirely separate domains in a production environment, cross-origin resource sharing (CORS) comes into play. Without handling CORS, requests from the front end to the back end will generally be blocked by your browser.
The django-cors-headers project makes dealing with CORS fairly painless. Youβll use this to tell Django to respond to requests even if they come from another origin, which will allow the front end to communicate properly with the GraphQL API.
Install django-cors-headers
First, add django-cors-headers to your requirements file:
django-cors-headers==3.6.0
Then install it using the updated requirements file:
(venv) $ python -m pip install -r requirements.txt
Add "corsheaders" to the INSTALLED_APPS list in your projectβs settings.py module:
INSTALLED_APPS = [
...
"corsheaders",
]
Then add "corsheaders.middleware.CorsMiddleware" to the end of the MIDDLEWARE variable:
MIDDLEWARE = [
"corsheaders.middleware.CorsMiddleware",
...
]
The django-cors-headers documentation suggests putting the middleware as early in the MIDDLEWARE list as possible. You can put it at the very top of the list in this project.
Configure django-cors-headers
CORS exists for a good reason. You donβt want to expose your application to be used from just anywhere on the Internet. You can use two settings to very precisely define how much you want to open up the GraphQL API:
CORS_ORIGIN_ALLOW_ALLdefines whether Django should be all open or all closed by default.CORS_ORIGIN_WHITELISTdefines which domains from which your Django application will allow requests.
Add the following settings to settings.py:
CORS_ORIGIN_ALLOW_ALL = False
CORS_ORIGIN_WHITELIST = ("http://localhost:8080",)
These settings will allow requests only from your front end, which youβll eventually run on port 8080 locally.
Step 4 Summary
The back end is complete! You have a working data model, a working admin interface, a working GraphQL API that you can explore using GraphiQL, and the ability to query the API from the front end youβll build next. This is a great place to pause if you havenβt taken a break in a while.
Step 5: Set Up Vue.js
Youβll use Vue as the front end for your blog. To set up Vue, youβll create the Vue project, install a couple of important plugins, and run the Vue development server to make sure your application and its dependencies are working together properly.
Create the Vue Project
Much like Django, Vue provides a command-line interface for creating a project without starting totally from scratch. You can pair this with Nodeβs npx command to bootstrap JavaScript-based commands others have published. Using this approach, you wonβt need to manually install the variety of individual dependencies that you need to get a Vue project up and running. Use npx to create your Vue project now:
$ cd /path/to/dvg/
$ npx @vue/cli create frontend --default
...
π Successfully created project frontend.
...
$ cd frontend/
This will create a frontend/ directory alongside your existing backend/ directory, install a number of JavaScript dependencies, and create some skeleton files for the application.
Install Vue Plugins
Youβll need some plugins for Vue to do proper browser routing and interact with your GraphQL API. These plugins sometimes affect your files, so itβs good to install them near the beginning so they donβt overwrite anything, then configure them later on. Install the Vue Router and Vue Apollo plugins, choosing the default options when prompted:
$ npx @vue/cli add router
$ npx @vue/cli add apollo
These commands will take some time to install dependencies, and theyβll add or change some of the files in the project to configure and install each plugin in your Vue project.
Step 5 Summary
You should now be able to run the Vue development server:
$ npm run serve
You now have your Django application running at http://localhost:8000 and your Vue application running at http://localhost:8080.
Visit http://localhost:8080 in your browser. You should see the Vue splash page, which indicates that youβve installed everything successfully. If you see the splash page, youβre ready to start creating some of your own components.
Step 6: Set Up Vue Router
An important part of client-side applications is handling routing without having to make new requests to the server. A common solution for this in Vue is the Vue Router plugin, which you installed earlier. Youβll use Vue Router instead of normal HTML anchor tags to link to different pages of your blog.
Create Routes
Now that youβve installed Vue Router, you need to configure Vue to use Vue Router. Youβll also need to configure Vue Router with the URL paths it should route.
Create a router.js module in the src/ directory. This file will hold all the configuration about which URLs map to which Vue components. Start by importing Vue and Vue Router:
import Vue from 'vue'
import VueRouter from 'vue-router'
Add the following imports, which each correspond to a component youβll create shortly:
import Post from '@/components/Post'
import Author from '@/components/Author'
import PostsByTag from '@/components/PostsByTag'
import AllPosts from '@/components/AllPosts'
Register the Vue Router plugin:
Vue.use(VueRouter)
Now youβll create the list of routes. Each route has two properties:
pathis a URL pattern that optionally contains capture variables similar to Django URL patterns.componentis the Vue component to display when the browser navigates to a route matching the path pattern.
Add these routes as a routes variable. They should look like the following:
const routes = [
{ path: '/author/:username', component: Author },
{ path: '/post/:slug', component: Post },
{ path: '/tag/:tag', component: PostsByTag },
{ path: '/', component: AllPosts },
]
Create a new instance of VueRouter and export it from the router.js module so other modules can use it:
const router = new VueRouter({
routes: routes,
mode: 'history',
})
export default router
Youβll import the router variable in another module in the next section.
Install the Router
At the top of src/main.js, import the router from the module you created in the previous section:
import router from '@/router'
Then pass the router to the Vue instance:
new Vue({
router,
...
})
This completes the configuration of Vue Router.
Step 6 Summary
Youβve created the routes for your front end, which map a URL pattern to the component that will show up at that URL. The routes wonβt work just yet because they point to components that donβt yet exist. Youβll create these components in the next step.
Step 7: Create the Vue Components
Now that youβve got Vue up and running with routes that will go to your components, you can start creating the components that will eventually display data from the GraphQL endpoint. At the moment, youβll just make them display some static content. The table below describes the components youβll create:
| Component | Displays |
|---|---|
AuthorLink |
A link to a given authorβs page (used in Post and PostList) |
PostList |
A given list of blog posts (used in AllPosts, Author, and PostsByTag) |
AllPosts |
A list of all posts, with the most recent first |
PostsByTag |
A list of posts associated with a given tag, with the most recent first |
Post |
The metadata and content for a given post |
Author |
Information about an author and a list of posts theyβve written |
Youβll update these components with dynamic data in the next step.
The AuthorLink Component
The first component youβll create displays a link to an author.
Create an AuthorLink.vue file in the src/components/ directory. This file is a Vue single-file component (SFC). SFCs contain the HTML, JavaScript, and CSS needed to properly render a component.
The AuthorLink accepts an author prop whose structure corresponds to the data about authors in your GraphQL API. The component should show the userβs first and last name, if provided, or show the userβs username otherwise.
Your AuthorLink.vue file should look like the following:
<template>
<router-link
:to="`/author/${author.user.username}`"
>{{ displayName }}</router-link>
</template>
<script>
export default {
name: 'AuthorLink',
props: {
author: {
type: Object,
required: true,
},
},
computed: {
displayName () {
return (
this.author.user.firstName &&
this.author.user.lastName &&
`${this.author.user.firstName} ${this.author.user.lastName}`
) || `${this.author.user.username}`
},
},
}
</script>
This component wonβt use GraphQL directly. Instead, other components will pass in the author information using the author prop.
The PostList Component
The PostList component accepts a posts prop, whose structure corresponds to the data about posts in your GraphQL API. The component also accepts a Boolean showAuthor prop, which youβll set to false on the authorβs page because itβs redundant information. The component should display the following features:
- The title and subtitle of the post, linking them to the postβs page
- A link to the author of the post using
AuthorLink(ifshowAuthoristrue) - The date the post was published
- The meta description for the post
- The list of tags associated with the post
Create a PostList.vue SFC in the src/components/ directory. The component template should look like the following:
<template>
<div>
<ol class="post-list">
<li class="post" v-for="post in publishedPosts" :key="post.title">
<span class="post__title">
<router-link
:to="`/post/${post.slug}`"
>{{ post.title }}: {{ post.subtitle }}</router-link>
</span>
<span v-if="showAuthor">
by <AuthorLink :author="post.author" />
</span>
<div class="post__date">{{ displayableDate(post.publishDate) }}</div>
<p class="post__description">{{ post.metaDescription }}</p>
<ul>
<li class="post__tags" v-for="tag in post.tags" :key="tag.name">
<router-link :to="`/tag/${tag.name}`">#{{ tag.name }}</router-link>
</li>
</ul>
</li>
</ol>
</div>
</template>
The PostList componentβs JavaScript should look like the following:
<script>
import AuthorLink from '@/components/AuthorLink'
export default {
name: 'PostList',
components: {
AuthorLink,
},
props: {
posts: {
type: Array,
required: true,
},
showAuthor: {
type: Boolean,
required: false,
default: true,
},
},
computed: {
publishedPosts () {
return this.posts.filter(post => post.published)
}
},
methods: {
displayableDate (date) {
return new Intl.DateTimeFormat(
'en-US',
{ dateStyle: 'full' },
).format(new Date(date))
}
},
}
</script>
The PostList component receives its data as a prop instead of using GraphQL directly.
You can add some optional CSS styling to make the list of posts a bit more readable once theyβre rendered:
<style>
.post-list {
list-style: none;
}
.post {
border-bottom: 1px solid #ccc;
padding-bottom: 1rem;
}
.post__title {
font-size: 1.25rem;
}
.post__description {
color: #777;
font-style: italic;
}
.post__tags {
list-style: none;
font-weight: bold;
font-size: 0.8125rem;
}
</style>
These styles add some spacing, remove some clutter, and differentiate different pieces of information to help with scannability.
The AllPosts Component
The next component youβll create is a list of all the posts on the blog. It needs to display two pieces of information:
- A Recent Posts heading
- The list of posts, using
PostList
Create the AllPosts.vue SFC in the src/components/ directory. It should look like the following:
<template>
<div>
<h2>Recent posts</h2>
<PostList v-if="allPosts" :posts="allPosts" />
</div>
</template>
<script>
import PostList from '@/components/PostList'
export default {
name: 'AllPosts',
components: {
PostList,
},
data () {
return {
allPosts: null,
}
},
}
</script>
Youβll populate the allPosts variable dynamically with a GraphQL query later in this tutorial.
The PostsByTag Component
The PostsByTag component is very similar to the AllPosts component. The heading text differs, and youβll query for a different set of posts in the next step.
Create the PostsByTag.vue SFC in the src/components/ directory. It should look like the following:
<template>
<div>
<h2>Posts in #{{ $route.params.tag }}</h2>
<PostList :posts="posts" v-if="posts" />
</div>
</template>
<script>
import PostList from '@/components/PostList'
export default {
name: 'PostsByTag',
components: {
PostList,
},
data () {
return {
posts: null,
}
},
}
</script>
Youβll populate the posts variable with a GraphQL query later in this tutorial.
The Author Component
The Author component acts as an authorβs profile page. It should display the following information:
- A heading with the authorβs name
- A link to the authorβs website, if provided
- The authorβs biography, if provided
- The list of posts by the author, with
showAuthorset tofalse
Create the Author.vue SFC in the src/components/ directory now. It should look like the following:
<template>
<div v-if="author">
<h2>{{ displayName }}</h2>
<a
:href="author.website"
target="_blank"
rel="noopener noreferrer"
>Website</a>
<p>{{ author.bio }}</p>
<h3>Posts by {{ displayName }}</h3>
<PostList :posts="author.postSet" :showAuthor="false" />
</div>
</template>
<script>
import PostList from '@/components/PostList'
export default {
name: 'Author',
components: {
PostList,
},
data () {
return {
author: null,
}
},
computed: {
displayName () {
return (
this.author.user.firstName &&
this.author.user.lastName &&
`${this.author.user.firstName} ${this.author.user.lastName}`
) || `${this.author.user.username}`
},
},
}
</script>
Youβll populate the author variable dynamically with a GraphQL query later in this tutorial.
The Post Component
Just like the data model, the Post component is the most interesting because it has the responsibility of displaying all the postβs information. The component should display the following information about the post:
- Title and subtitle, as a heading
- Author, as a link using
AuthorLink - Publication date
- Meta description
- Content body
- List of associated tags, as links
Because of your data modeling and component architecture, you may be surprised at how little code this requires. Create the Post.vue SFC in the src/components/ directory. It should look like the following:
<template>
<div class="post" v-if="post">
<h2>{{ post.title }}: {{ post.subtitle }}</h2>
By <AuthorLink :author="post.author" />
<div>{{ displayableDate(post.publishDate) }}</div>
<p class="post__description">{{ post.metaDescription }}</p>
<article>
{{ post.body }}
</article>
<ul>
<li class="post__tags" v-for="tag in post.tags" :key="tag.name">
<router-link :to="`/tag/${tag.name}`">#{{ tag.name }}</router-link>
</li>
</ul>
</div>
</template>
<script>
import AuthorLink from '@/components/AuthorLink'
export default {
name: 'Post',
components: {
AuthorLink,
},
data () {
return {
post: null,
}
},
methods: {
displayableDate (date) {
return new Intl.DateTimeFormat(
'en-US',
{ dateStyle: 'full' },
).format(new Date(date))
}
},
}
</script>
Youβll populate the post variable dynamically with a GraphQL query later in this tutorial.
The App Component
Before you can see the outcomes of your labor, you need to update the App component that your Vue setup command created. Instead of showing the Vue splash page, it should show the AllPosts component.
Open the App.vue SFC in the src/ directory. You can delete all the content inside it because youβll need to replace it with code that displays the following features:
- A heading with the title of your blog that links to the home page
<router-view>, a Vue Router component that renders the right component for the current route
Your App component should look like the following:
<template>
<div id="app">
<header>
<router-link to="/">
<h1>Awesome Blog</h1>
</router-link>
</header>
<router-view />
</div>
</template>
<script>
export default {
name: 'App',
}
</script>
You can also add some optional CSS styling to polish up the display a bit:
<style>
* {
margin: 0;
padding: 0;
}
body {
margin: 0;
padding: 1.5rem;
}
* + * {
margin-top: 1.5rem;
}
#app {
margin: 0;
padding: 0;
}
</style>
These styles give a bit of breathing room to most elements on the page and remove the space around the whole page that most browsers add by default.
Step 7 Summary
If you havenβt used Vue much before, this step might have been a lot to digest. Youβve reached an important milestone, though. You have a working Vue application, complete with routes and views ready to display data.
You can confirm your application is working by starting the Vue development server and visiting http://localhost:8080. You should see the title of your blog and a Recent Posts heading. If you do, then youβre ready to take on the final step, where youβll use Apollo to query your GraphQL API to bring the front end and back end together.
Step 8: Fetch the Data
Now that youβve got everything prepped for displaying data when itβs available, itβs time to fetch that data from your GraphQL API.
Apollo makes querying GraphQL APIs more convenient. The Vue Apollo plugin you installed earlier integrates Apollo into Vue, making it that much more convenient to query GraphQL from within your Vue project.
Configure Vue Apollo
Vue Apollo is mostly configured out of the box, but youβll need to tell it the right endpoint to query. You may also want to turn off the WebSocket connection it tries to use by default because this generates noise in the Network and Console tabs of your browser. Edit the apolloProvider definition in the src/main.js module to specify the httpEndpoint and wsEndpoint properties:
new Vue({
...
apolloProvider: createProvider({
httpEndpoint: 'http://localhost:8000/graphql',
wsEndpoint: null,
}),
...
})
Now youβre ready to start adding the queries to populate your pages. Youβll do this by adding a created() function to several of your SFCs. created() is a special Vue lifecycle hook that executes when a component is about to render on the page. You can use this hook to query for the data you want to render so that it becomes available as your component renders. Youβll create a query for the following components:
PostAuthorPostsByTagAllPosts
You can start by creating the Post query.
The Post Query
The query for an individual post accepts the slug of the desired post. It should return all the necessary pieces of information to display the post information and content.
Youβll use the $apollo.query helper and the gql helper to build the query in the Post componentβs created() function, ultimately using the response to set the componentβs post so it can be rendered. created() should look like the following:
<script>
import gql from 'graphql-tag'
...
export default {
...
async created () {
const post = await this.$apollo.query({
query: gql`query ($slug: String!) {
postBySlug(slug: $slug) {
title
subtitle
publishDate
metaDescription
slug
body
author {
user {
username
firstName
lastName
}
}
tags {
name
}
}
}`,
variables: {
slug: this.$route.params.slug,
},
})
this.post = post.data.postBySlug
},
...
}
</script>
This query pulls in most of the data about the post and its associated author and tags. Notice that the $slug placeholder is used in the query, and the variables property passed to $apollo.query is used to populate the placeholder. The slug property matches the $slug placeholder by name. Youβll see this pattern again in some of the other queries.
The Author Query
Whereas in the query for Post you fetched the single postβs data and some nested data about the author, in the Author query youβll need to fetch the author data and the list of all posts by the author.
The author query accepts the username of the desired author and should return all the necessary pieces of information to display the author and their list of posts. It should look like the following:
<script>
import gql from 'graphql-tag'
...
export default {
...
async created () {
const user = await this.$apollo.query({
query: gql`query ($username: String!) {
authorByUsername(username: $username) {
website
bio
user {
firstName
lastName
username
}
postSet {
title
subtitle
publishDate
published
metaDescription
slug
tags {
name
}
}
}
}`,
variables: {
username: this.$route.params.username,
},
})
this.author = user.data.authorByUsername
},
...
}
</script>
This query uses postSet, which might look familiar if youβve done some Django data modeling in the past. The name βpost setβ comes from the reverse relationship Django creates for a ForeignKey field. In this case, the post has a foreign key relationship to its author, which has a reverse relationship with the post called post_set. Graphene-Django has automatically exposed this as postSet in the GraphQL API.
The PostsByTag Query
The query for PostsByTag should feel pretty similar to the first queries you created. This query accepts the desired tag and returns the list of matching posts. created() should look like the following:
<script>
import gql from 'graphql-tag'
...
export default {
...
async created () {
const posts = await this.$apollo.query({
query: gql`query ($tag: String!) {
postsByTag(tag: $tag) {
title
subtitle
publishDate
published
metaDescription
slug
author {
user {
username
firstName
lastName
}
}
tags {
name
}
}
}`,
variables: {
tag: this.$route.params.tag,
},
})
this.posts = posts.data.postsByTag
},
...
}
</script>
You might notice that some pieces of each query look pretty similar to one another. Although it wonβt be covered in this tutorial, you can use GraphQL fragments to reduce duplication in your query code.
The AllPosts Query
The query for AllPosts doesnβt require any input information and returns the same set of information as the PostsByTag query. It should look like the following:
<script>
import gql from 'graphql-tag'
export default {
...
async created () {
const posts = await this.$apollo.query({
query: gql`query {
allPosts {
title
subtitle
publishDate
published
metaDescription
slug
author {
user {
username
firstName
lastName
}
}
tags {
name
}
}
}`,
})
this.allPosts = posts.data.allPosts
},
...
}
</script>
This is the last query for now, but you should revisit the last couple of steps to let them sink in. If you want to add new pages with new views of your data in the future, itβs a matter of creating a route, a component, and a query.
Step 8 Summary
Now that each component is fetching the data it needs to display, youβve arrived at a functioning blog. Run the Django development server and the Vue development server. Visit http://localhost:8080 and browse through your blog. If you can see authors, posts, tags, and the content of a post in your browser, youβre golden!
Next Steps
You started by creating a Django blog back end to administer, persist, and serve the data for a blog. Then you created a Vue front end to consume and display that data. You made the two communicate with GraphQL using Graphene and Apollo.
You may already be wondering what you can do next. To further validate that your blog is working as expected, you could try the following:
- Add more users and posts to see them split up by author.
- Make some posts unpublished to confirm that they donβt show up on the blog.
If youβre feeling confident and adventurous with what you have going, you can also take this system of yours even further:
- Expand your data model to create new behavior in your Django blog.
- Create new queries to provide interesting views on your blogβs data.
- Explore GraphQL mutations to write data in addition to reading it.
- Add CSS to your single-file components to make the blog more eye-catching.
The data modeling and component architecture youβve put together is remarkably extensible, so take it as far as you like!
If you want to make your Django application ready for prime time, read Deploying Django + Python3 + PostgreSQL to AWS Elastic Beanstalk or Development and Deployment of Django on Fedora. You can use Amazon Web Services or something like Netlify to deploy your Vue project as well.
Conclusion
Youβve seen how you can use GraphQL for building typed, flexible views of your data. You can use these same techniques on an existing Django application youβve built or one you plan to build. Like other APIs, you can use yours in most any client-side framework as well.
In this tutorial, you learned how to:
- Build the Django blog data model and admin interface
- Wrap your data model in a GraphQL API using Graphene-Django
- Create and route to separate Vue components for each view of the data
- Query the GraphQL API dynamically to populate your Vue components using Apollo
You covered a lot of ground, so try to identify some new ways to use these concepts in different contexts to solidify your learning. Happy coding, and happy blogging!
You can download the complete source code for this project by clicking the link below:
Get the Source Code: Click here to get the source code youβll use to build a blog application with Django, Vue, and GraphQL in this tutorial.




