Integrate Django and Vue.js
With Python gaining popularity and Vue.js taking off, more developers are looking to use the two frameworks together to build single-page applications (SPAs).
There are helpful guides out there demonstrating how to run Vue.js inside a Django application. The most popular write-up I found on the subject is this Medium post by Rodrigo Smaniotto. The post walks through how to run the Vue dev server inside a Django template, which is pretty cool, and is great for experimenting with Django and Vue. The author uses django-webpack-loader to include a Webpack-bundled Vue.js application into a Django template using template tags. It's pretty slick, but doesn't cover production deployments.
The author mentioned planning to write a post that would detail a production-ready Django-Vue integration, but I found myself needing to deploy a Django+Vue app in a hurry.
I came up with a strategy for a Django-Vue integration that works in both development and production environments. As a bonus, it requires no extra dependencies.
My approach involves three parts:
- Configure Vue to use the Django dev server for local development and the Django production server in production
- Configure Django to serve the production template of a Vue.js application as its homepage
- Configure Django and Vue to serve Vue's static files in production (images, CSS, JS)
Let's get started.
Demo app
Well, maybe we won't get started quite yet. I put together a demo application in my blog examples repo to show how to use a Vue.js front end in Django web app development.
It's a simple Vue application that displays a list of phrases fetched from a Django API:
Poking around that repo could help contextualize the code examples in this post. For reference, here's a simplified file structure of the project specifying the files and directories mentioned below:
integrate-django-vuejs
βββ django_vue/
β βββ settings.py # Django settings
βββ frontend/ # Vue project
β βββ dist/ # Webpack build folder
β | βββ static/ # Vue assets
| β βββ index.html # Vue HTML file
β βββ package.json # Node dependencies
β βββ vue.config.js # Vue configuration
βββ static/ # Django's collected static files
β βββ static/ # Vue's collected assets
β βββ index.html # Vue's collected HTML file
βββ manage.py*
Let's get startedβfor real, this time!
Development
The trick to developing a Vue application that uses a JSON APIβor any APIβis to ensure that the Vue app can reach the API. That may sound obvious, but because development APIs and production APIs typically have different URLs, it can be annoying to keep the API root URL straight.
The first bit of magic we're going to do is to configure the Vue dev server to look for the Django server for API endpoints.
By default, the Django dev server runs on port 8000
and the Vue dev server runs on port 8080
. With Vue's dev server proxy setting, we can configure Vue to try another URL if a request 404s:
// vue.config.js
module.exports = {
devServer: {
proxy: {
"^/api/": {
target: "http://127.0.0.1:8000/api/",
},
},
},
};
With this configuration in place, let's make an API call:
// This function uses vue-resource to help with API calls:
// https://github.com/pagekit/vue-resource/
async fetchPhrases() {
const response = await this.$http.get('/api/phrases/')
return response.body.data
}
When we make the request to /api/phrases/
, it will try to GET
localhost:8080/api/phrases/
because we're omitting a domain in our API request. That request will 404, so Vue will use its dev server proxy to proxy the request to 127.0.0.1:8000/api/phrases/
, hitting the Django dev server.
Great! We've got Vue hitting the Django dev API without having to fuss over different hosts or ports. Now let's get them working together in production.
Production
In production, Django does the heavy lifting. It hosts the JSON API and acts as the web server that serves the Webpack-built Vue application.
Configuring Django for production Vue
With a bit of configuration, we can use Django's built-in template and static file support to serve a production Vue application.
Let's take a look at that configuration.
Settings
First up, the Django settings file:
# settings.py
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Directory where Django static files are collected
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
# URL where static files will be served
STATIC_URL = '/static/'
# Vue project location
FRONTEND_DIR = os.path.join(BASE_DIR, 'frontend')
# Vue assets directory (assetsDir)
STATICFILES_DIRS = [
os.path.join(FRONTEND_DIR, 'dist/static'),
]
# Webpack output location containing Vue index.html file (outputDir)
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
os.path.join(FRONTEND_DIR, 'dist'),
],
# Other TEMPLATE keys omitted for brevity
},
]
This abbreviated settings.py
file does two things of note:
- adds the Webpack built assets directory,
frontend/dist/static
, to Django's list of directories containing static files - adds the Webpack build directory to Django's template paths so Django can find the production
index.html
Vue template
Speaking of that index.html
file, let's make a simple view to serve it, and a simple API endpoint while we're at it:
Views
The first view renders the production Vue application template. The second view is a stand-in for a proper API, and it returns a JSON response.
# views.py
from django.http import JsonResponse
from django.shortcuts import render
def index(request):
return render(request, template_name='index.html')
def simple_api_view(request):
response = JsonResponse({
'data': [
'You get an phrase from the API!',
'And you get a phrase from the API!',
'And you get a phrase from the API!',
'And you get a phrase from the API!',
'And you get a phrase from the API!',
]
})
return response
(In your project, I hope that your API views are more interestingβbut I won't judge.)
We'll register these views, too.
URLs
from django.urls import path
from django_vue.views import index, simple_api_view
urlpatterns = [
path('', index, name='index'),
path('api/phrases/', simple_api_view, name='phrases'),
]
Okay! Now, as long as the Vue application is in a directory named frontend
, and Webpack puts the built Vue application in frontend/static/
, Django will find and serve the built Vue application.
We'd better let Vue know.
Configuring Vue for production Django
Settings
// frontend/vue.config.js
module.exports = {
devServer: {
proxy: {
"^/api/": {
target: "http://127.0.0.1:8000/api/",
ws: false,
},
},
},
// outputDir must be added to Django's TEMPLATE_DIRS
outputDir: "./dist/",
// assetsDir must match Django's STATIC_URL
assetsDir: "static",
};
Here's a quick summary of this configuration:
Configuration | Explanation |
---|---|
devServer.proxy | redirects dev server API requests to the Django dev server |
outputDir: './dist/' | specifies where Webpack will build the Vue application |
assetsDir: 'static' | updates JS & CSS locations to where Django serves them |
The assetsDir: 'static'
might benefit from further explanation.
Out of the box, Vue's Webpack build expects to find its CSS and JS files at /css/
and /js/
URLs, respectively. Because we're using Django to serve these files, and Django serves static files at a STATIC_URL
, we update assetsDir
to match Django's static URL. In this case, that's /static/
.
With this setting, the production Vue index.html
is built with the correct paths to the Webpack-built resource bundles (/static/css/
and /static/js/
).
Preparing a production build
Preparing a production build should be familiar.
First, build the production Vue.js build:
cd frontend
yarn build
Second, collect that build into Django's static files folder:
./manage.py collectstatic
Now, Django will serve the production Vue application as its homepage.
Conclusion
With a small amount of configuration, Django and Vue can play together nicely during web app development and when you're ready to go live.
A secondary benefit of this method is that when developing a new feature or fixing a bug, you can run your production front end and development front end side-by-side, with Django's dev server running the production version and Vue's dev server running the work-in-progress.
This post treated the Vue.js application and Django API as discrete projects living under the same roof, but it doesn't have to be that way.
It could be fun to get the projects working together a bit, like by making the Django view that serves Vue's index.html
file login-required.
Maybe it would even be possible to share template partials across Django and Vue. They do use the same {{ variable }}
syntax, after all.
I'd better stop before I give myself too many ideas.