Integrate Django and Vue.js

Django plus Vue.js
Django plus 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:

  1. Configure Vue to use the Django dev server for local development and the Django production server in production
  2. Configure Django to serve the production template of a Vue.js application as its homepage
  3. 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:

Django + Vue app from my blog examples repo
Django + Vue app from my blog examples repo

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:

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:

ConfigurationExplanation
devServer.proxyredirects 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.