Hooking up Django and Vue.js
Tags: django, vue, python, webdev
Published: 2020-04-23 03:24:40

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/p8g0yi9gp5x41bf1c76r.png)

This article is for individuals who have had intermediate practice with the Vue.js and Django separately, but need direction on how to make these two frameworks work together. I will outline an example of how to set up some boilerplate code with a straightforward project structure using Django and Vue.js. Please note I am developing on macOS Catalina, so the shell commands will vary for your operating system. The finished product can be found on my github: https://github.com/jordanengstrom/blank_django_vue_project

At a high level we will be aiming for a project structure that looks something like this:
```
my_project/
|
|____ core/
|    |____ __init__.py
|    |____ views.py           # class based TemplateView for index.html
|    
|
|____ frontend/               # root frontend
|    |____ src/               # vue components, router, store, etc.
|    |____ node_modules/
|    |____ vue.config.js      # important file number 1
|    |____ webpack-stats.json # important file number 2
|
|
|____ my_project/             # root backend
|    |____ __init__.py
|    |____ settings.py
|    |____ urls.py
|    |____ views.py
|
|
|____ templates/                
|    |____ index.html         # django template that houses vue
|
|____ .gitignore
|____ venv/
|____ requirements.txt
|____ manage.py
|____ db.sqlite3
```


We'll start with the backend. Create an empty directory for your project, and then run:

```sh
$ django-admin startproject my_project && cd my_project
$ mkdir venv && python3 -m venv venv && source venv/bin/activate
$ (venv) pip install django djangorestframework
$ (venv) mkdir templates && cd templates && touch index.html
$ (venv) cd ..
$ (venv) mkdir core && touch __init__.py && touch views.py
```
This is all we need to do with Django for now. We'll come back to the templates/index.html and core/views.py files later. This next command assumes you've already installed vue globally. If you haven't, simply run: npm install -g vue-cli

```sh
$ vue create frontend
```
Then walk through the setup wizard so your presets look something like this (or customize your presets as you prefer):
```
$ Vue CLI v4.3.1
  ? Please pick a preset: Manually select features
  ? Check the features needed for your project: Babel, Router, Vuex
  ? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
  ? Where do you prefer placing config for Babel, ESLint, etc.? In package.json
  ? Save this as a preset for future projects? (y/N) N

  ### installation magic happens...

🎉  Successfully created project frontend.
👉  Get started with the following commands:
$ cd frontend
$ npm run serve
```
Now we have separate frontend and backend environments! The frontend server will be at http://localhost:8080/ which will display the Vue landing page, and the backend will be at http://127.0.0.1:8000/ which will display the Django landing page. You can kick off the frontend per above commands and kick off the backend in the root directory with:
```sh
python manage.py runserver
```
Now, these environments just need to be able to talk to each other. The two tools we will use to accomplish this are webpack-bundle-tracker on the Vue side and Django webpack loader on the backend.
```sh
$ cd frontend
$ npm install webpack-bundle-tracker@0.4.3
```
We need version 0.4.3 of webpack-bundle-tracker because the files Django is expecting will not be automatically generated in the same way if we use the latest alpha version. Please note, 0.4.3 is the latest stable release as of April 2020. If we don't specify this version, npm will pull down the latest alpha version which will not work the same. Next we need to create the vue.config.js file in the frontend directory:
```sh
$ touch vue.config.js
```
and fill it with these contents:
```javascript
const BundleTracker = require("webpack-bundle-tracker");

module.exports = {
  // on Windows you might want to set publicPath: "http://127.0.0.1:8080/"
  publicPath: "http://0.0.0.0:8080/",
  outputDir: "./dist/",

  chainWebpack: (config) => {
    config
      .plugin("BundleTracker")
      .use(BundleTracker, [{ filename: "./webpack-stats.json" }]);

    config.output.filename("bundle.js");

    config.optimization.splitChunks(false);

    config.resolve.alias.set("__STATIC__", "static");

    config.devServer
      // the first 3 lines of the following code have been added to the configuration
      .public("http://127.0.0.1:8080")
      .host("127.0.0.1")
      .port(8080)
      .hotOnly(true)
      .watchOptions({ poll: 1000 })
      .https(false)
      .disableHostCheck(true)
      .headers({ "Access-Control-Allow-Origin": ["*"] });
  }

  // uncomment before executing 'npm run build'
  // css: {
  //     extract: {
  //       filename: 'bundle.css',
  //       chunkFilename: 'bundle.css',
  //     },
  // }
};
```


Comment out the base url settings that comes with Vue's router. If you skip this, you'll just end up having a path like this: http://127.0.0.1:8000/http:/0.0.0.0:8080/blah-blah-blah
to which I say - ew wtf? 
Fix it by removing the base url config:
```javascript
const router = new VueRouter({
  mode: "history",
  // base: process.env.BASE_URL,
  routes
});
```
Once you create this file, spin up the frontend development server, and a webpack-stats.json file will be generated
```sh
npm run serve
```
Now navigate to the root directory and ensure your virtual environment is activated so we can install django-webpack-loader. Also, feel free to generate your requirements file: 
```sh
$ (venv) pip install django-webpack-loader
$ pip freeze > requirements.txt
```
Then, add webpack_loader to Django's installed apps in settings.py, and add the following changes to the settings.py file:
```python
INSTALLED_APPS = [
         ...
    'rest_framework',
    'webpack_loader',
]

    .
    .
    .
TEMPLATES = [
    {               ...
                    
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
                    ...
    },
]
    .
    .
    .

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, "assets"),
    os.path.join(BASE_DIR, "frontend/dist"),
]

WEBPACK_LOADER = {
    'DEFAULT': {
        'BUNDLE_DIR_NAME': 'dist/',
        'STATS_FILE': os.path.join(BASE_DIR, 'frontend', 'webpack-stats.json')
    }
}

```
Paste this into your templates/index.html file:
```html
{% load render_bundle from webpack_loader %}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <!-- This part is in the screenshot at the bottom! -->
    <h1>Vue JS</h1>
    <div id="app"></div>
    {% render_bundle 'app' %}
  </body>
</html>
```
Paste this into your core/views.py file:
```python
from django.conf import settings
from django.views.generic.base import TemplateView


class IndexTemplateView(TemplateView):
    def get_template_names(self):
        template_name = "index.html"
        return template_name
```
Make sure these are reflected in your my_project/urls.py file:
```python
from django.urls import re_path
from core.views import IndexTemplateView

urlpatterns = [
    ...
    re_path(r"^.*$", IndexTemplateView.as_view(), name="entry-point"),
]

```
Once these changes are made, spin up your npm server in one terminal tab, and then spin up your Django dev server in another terminal tab and you should be good to go! Happy coding 😎
```sh
$ npm run serve
```
```sh
$ (venv) python manage.py runserver
```
![hooked up screenshot](https://i.imgur.com/pvAL0K5.png)