Django and Vue 3 with Webpack Loader and typescript support
Tags: django, typescript, vue3, webpack
Published: 2020-05-09 15:14:31

_Integrating Django as a backend to a running Vue App._

The version of Vue 3 has been available for some time now!
While I’m writing this post the 3.2 version has just been released.

Let’s begin.
I am assuming that you have already the powerful version manager **asdf** installed. And that you have basic understanding of Django and Nodejs package handling.

## Create a virtual environment with Nodejs, yarn, python 3, PostgreSQL, Django and create a Django Application

You will need a compiler.
Ubuntu:
- sudo apt install linux-headers-$(uname -r) build-essential

On Ubuntu, you will need libreadline
- sudo apt-get install libreadline-dev

On Ubuntu 20.04, you will need curl and zlib
- sudo apt-get install zlib1g-dev curl

```bash
asdf plugin-add nodejs
asdf plugin-add yarn
asdf plugin-add python
asdf plugin-add postgres

# Import the Node.js release team's OpenPGP keys to main keyring:
bash ~/.asdf/plugins/nodejs/bin/import-release-team-keyring

# create virtual env for all
mkdir venv-django
cd venv-django

# install all latest versions
~/venv-django asdf install nodejs latest
~/venv-django asdf install yarn latest
~/venv-django asdf install python latest
~/venv-django asdf install postgres latest

# set .tool-versions in your virtual env
# if you don't remember the latest version you can use  
# `asdf list postgres` for example
~/venv-django asdf local nodejs 14.2.0
~/venv-django asdf local yarn 1.22.4
~/venv-django asdf local python 3.8.2
~/venv-django asdf local postgres 12.2

# check if all good
cat .tool-versions

~/venv-django node -v
v14.2.0

~/venv-django yarn -v
1.22.4

~/venv-django python --version
Python 3.8.2

~/venv-django postgres --version
postgres (PostgreSQL) 12.2

# start postgres
pg_ctl start

# create database
~/venv-django createdb django

#test connection
~/venv-django psql -d django
django=# exit
```

Now, you should be ready to create the project backend:

```bash
~/venv-django pip install django

# reshim v-env
~/venv-django asdf reshim

# create Django project
~/venv-django django-admin startproject mysite
~/venv-django cd mysite
```

Configure django to use postgres

```bash
~/venv-django pip install psycopg2-binary
```

```python
# mysite/mysite/settings.py

. . .

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'django',
        'USER': '',
        'PASSWORD': '',
        'HOST': 'localhost',
        'PORT': '',
    }
}

. . .
```

Run migrations
```bash
~/venv-django/mysite python manage.py migrate
```

test backend server: `~/venv-django/mysite python manage.py runserver` and navigate to  [http://127.0.0.1:8000/](http://127.0.0.1:8000/)

At this point, you should see:
![Django default page](https://i.stack.imgur.com/MzXfs.png "Django default page")

Hit CONTROL-C, and now, you should be ready to create the frontend project:

```bash
~/venv-django/mysite mkdir frontend && cd frontend

# init the frontend project and hit enter for all questions
~/venv-django/mysite/frontend yarn init
yarn init v1.22.4
question name (frontend): 
question version (1.0.0): 
question description: 
question entry point (index.js): 
question repository url: 
question author: 
question license (MIT): 
question private: 
success Saved package.json
Done in 3.56s.
```

Install the dependencies:
```bash
~/venv-django/mysite/frontend yarn add vue@next webpack-bundle-tracker@0.4.3
~/venv-django/mysite/frontend yarn add --dev yarn vue-loader@v16.8.2 webpack-cli webpack webpack-dev-server typescript ts-loader @vue/compiler-sfc
```

Create a webpack.config.js in your frontend directory:

```js
// frontend/webpack.config.js

const path = require('path')
const { VueLoaderPlugin } = require('vue-loader')
const BundleTracker = require('webpack-bundle-tracker');

module.exports = (env = {}) => {
  return {

    mode: env.prod ? 'production' : 'development',
    devtool: env.prod ? 'source-map' : 'cheap-module-eval-source-map',
    entry: path.resolve(__dirname, './src/main.ts'),
    output: {
      path: path.resolve(__dirname, './dist'),
    },
    module: {
      rules: [
        {
          test: /\.vue$/,
          use: 'vue-loader'
        },
        {
          test: /\.ts$/,
          loader: 'ts-loader',
          options: {
            appendTsSuffixTo: [/\.vue$/],
          }
        },
      ]
    },
    resolve: {
      extensions: ['.ts', '.js', '.vue', '.json'],
      alias: {
        'vue': '@vue/runtime-dom'
      }
    },
    plugins: [
      new VueLoaderPlugin(),
      new BundleTracker({
        filename: './webpack-stats.json'
        publicPath: 'http://0.0.0.0:8080/'
      })
    ],
    devServer: {
      headers: {
        "Access-Control-Allow-Origin":"\*"
      },
      public: 'http://0.0.0.0:8080',
      inline: true,
      hot: true,
      stats: "minimal",
      contentBase: __dirname,
      overlay: true
    }
  };
}
```

we need to add a tsconfig.json file with the following rules in the frontend directory:
```json
{
  "compilerOptions": {
    "allowJs": true,
    "allowSyntheticDefaultImports": true,
    "declaration": false,
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "module": "es2015",
    "moduleResolution": "node",
    "noImplicitAny": false,
    "noLib": false,
    "sourceMap": true,
    "strict": true,
    "strictPropertyInitialization": false,
    "suppressImplicitAnyIndexErrors": true,
    "target": "es2015",
    "baseUrl": "."
  },
  "exclude": [
    "./node_modules"
  ],
  "include": [
    "./src/**/*.ts",
    "./src/**/*.vue"
  ]
}
```

Add the launch command to `package.json`:

```json
{

. . .


"scripts": {
    "serve": "webpack-dev-server"
  }
}
```

Create our vue 3 app:
```bash
~/venv-django/mysite/frontend mkdir src/ && cd src/

# Entry file
~/venv-django/mysite/frontend/src touch main.ts

#ts definitions for *.vue files
~/venv-django/mysite/frontend/src touch shims-vue.d.ts

# Default vue 3 component
~/venv-django/mysite/frontend/src touch App.vue
```

`main.ts`:

```typescript
// frontend/src/main.ts

import {createApp} from 'vue';
import App from './App.vue';

createApp(App).mount('#app');
```
`shims-vue.d.ts` to avoid error with *.vue files:

```typescript
// frontend/src/shims-vue.d.ts

declare module "*.vue" {
    import { defineComponent } from "vue";
    const Component: ReturnType<typeof defineComponent>;
    export default Component;
}
```

And finally our component `App.vue`:
```vue
<!-- frontend/src/App.vue -->


<template>
  <h2>This is a Vue 3 component!</h2>
  <button @click="increase">Clicked {{ count }} times.</button>
</template>
<script lang="ts">
import {defineComponent, ref} from "vue";
export default defineComponent({
  setup() {
    const count = ref(0)
    const increase = () => {
      count.value++
    }

    return {
      count,
      increase,
    }
  }
});
</script>
```

Test the project
```bash
yarn serve          
yarn run v1.22.4
$ webpack-dev-server
ℹ ｢wds｣: Project is running at http://0.0.0.0:8080/
ℹ ｢wds｣: webpack output is served from http://0.0.0.0:8080/
ℹ ｢wds｣: Content not from webpack is served from /home/julien/venv-django/mysite/frontend
ℹ ｢wdm｣:    44 modules
ℹ ｢wdm｣: Compiled successfully.
```

Hit CONTROL-C and Congratulation now you have frontend and backend !!!
But sorry, they are not link yet, let's do it:

```bash
~/venv-django/mysite/frontend/src cd ../../

# Install webpack loader
pip install django-webpack-loader==0.6.0
```

Let's configure Django by setting the frontend dir in `settings.py`:

```python
# mysite/mysite/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__)))

# ADD THESE TWO LINES
TEMPLATES_DIR = os.path.join(BASE_DIR, 'templates')
FRONTEND_DIR = os.path.join(BASE_DIR, 'frontend')

. . .
```
Add `webpack_loader` to your installed modules in `settings.py`:
```python
# mysite/mysite/settings.py

. . .

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'webpack_loader',
]

. . .
```

Add `TEMPLATES_DIR` to your templates' config in `settings.py`:
```python
# mysite/mysite/settings.py

. . .

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [TEMPLATES_DIR, ],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

. . .
```

Add webpack-loader config at the end of the `settings.py`:

```python
# mysite/mysite/settings.py

. . .

WEBPACK_LOADER = {
    'DEFAULT': {
        'CACHE': DEBUG,
        'BUNDLE_DIR_NAME': '/bundles/',  # must end with slash
        'STATS_FILE': os.path.join(FRONTEND_DIR, 'webpack-stats.json'),
    }
}
```

Create templates' directory:

```bash
~/venv-django/mysite mkdir templates
~/venv-django/mysite touch templates/application.html
```

Create main template inside:
```html
<!-- mysite/templates/application.html -->

{% load render_bundle from webpack_loader %}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title>Django Vue Integration</title>
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Material+Icons">
  </head>
  <body>
    <noscript>
      <strong>We're sorry but frontend doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
<div id="app">
     <app id="inspire"></app>
 </div>
{% render_bundle 'main' %}
<!-- built files will be auto injected -->

</body>
</html>
```
 And finally add the route in `urls.py`

```python
# mysite/mysite/urls.py

from django.conf import settings
from django.contrib import admin
from django.urls import path, include
from django.views.generic import TemplateView

urlpatterns = [
    path('admin/', admin.site.urls),
    path("",
         TemplateView.as_view(template_name="application.html"),
         name="app",
         ),
]
```

Now you can start the apps in two different tabs:

```bash
~/venv-django/mysite/frontend yarn serve                                             
yarn run v1.22.4
$ webpack-dev-server
ℹ ｢wds｣: Project is running at http://0.0.0.0:8080/
ℹ ｢wds｣: webpack output is served from http://0.0.0.0:8080/
ℹ ｢wds｣: Content not from webpack is served from /home/julien/venv-django/mysite/frontend
ℹ ｢wdm｣:    44 modules
ℹ ｢wdm｣: Compiled successfully.
```
```bash
~/venv-django/mysite python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
May 09, 2020 - 14:48:49
Django version 3.0.6, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
```

navigate to  [http://127.0.0.1:8000/](http://127.0.0.1:8000/)

You should see:
![Django default page](https://i.imgur.com/hq7eBZd.png "Django default page")

Congratulation and enjoy !!!

Tips:
- You can stop the postgres server wit `pg_ctl stop`
- Code of this tutorial: [django-vue3-example](https://github.com/clifinger/django-vue3-example)