Combine Django, Vue.js, Vuetify and TypeScript
Let’s combine Django, Vue.js, Vuetify and TypeScript into a complete toolchain for your next web project 🚀🚀
Benefits:
- Automatic reloading of front-end (TypeScript, Vue.js, Vuetify) and back-end (Python, Django) during development for quick code iterations
- Django got you covered for web development, from excellent database tools to user authentication, and everything between
- Vue.js makes it easy to write interactive user interfaces
- Vuetify gives you a toolbox of graphical user interface components that follow the Material Design-framework
- TypeScript extends JavaScript with typing to catch JavaScript errors before running the code
- You’ll have a self-contained application to deploy in production with static assets. No separate front-end project.
Notes:
- You need to have Python ≥ 3.6 and Node.js installed
- We’ll use Vue.js 2.x since Vue.js 3 is still in alpha
- You can use Flask or other Python web frameworks instead of Django. Actually, you could use any web framework, including Actix Web for Rust, since the same principles apply for connecting the front-end with the back-end.
- This tutorial uses whitenoise with Django to serve static assets in production builds. You might want to switch to a CDN if you have a high-traffic system. You just need to configure the DEPLOYMENT_PATH variable in vue.config.js to match your CDN URL addresses.
Alright, let’s go. First we need the official Vue.js standard tooling and to create a tutorial project folder.
# Install the official Vue.js command line tool
npm install -g @vue/cli
# Create a project folder
mkdir tutorial
Create Vue.js front-end
Create a new Vue.js 2.x project.
# current directory: tutorial
# Create a new Vue.js 2.x project in tutorial/client
vue create client
# Choose "Manually select features"
# Select TypeScript with spacebar
# Press enter
# Choose Vue version 2.x
# Use class-style component syntax?: n
# Use Babel alongside TypeScript...: Y
# Linter: ESLint with error prevention only
# Pick additional lint features: Lint on save
# Where do you prefer...: In dedicated config files
# Save this as a preset for future projects?: N
# Install Vuetify
cd client
vue add vuetify # Choose the default option on prompt
# Tracker to connect Django and webpack bundles
npm install -D webpack-bundle-tracker
Replace the existing content in tutorial/client/vue.config.js
with the following:
const BundleTracker = require("webpack-bundle-tracker");
const DEPLOYMENT_PATH = '/static/'
module.exports = {
runtimeCompiler: true,
pages: {
index: {
entry: "./src/main.ts",
chunks: ["chunk-vendors"]
}
},
publicPath: process.env.PROJECT_MODE === 'production' ? DEPLOYMENT_PATH : 'http://localhost:8080/',
outputDir: "../server/example/static/",
chainWebpack: config => {
config.optimization.splitChunks(false)
config.plugin('BundleTracker').use(BundleTracker, [{filename: '../server/webpack-stats.json'}])
config.resolve.alias.set('__STATIC__', 'static')
config.devServer
.public('http://0.0.0.0:8080')
.host('0.0.0.0')
.port(8080)
.hotOnly(true)
.watchOptions({poll: 1000})
.https(false)
.headers({"Access-Control-Allow-Origin": ["*"]})
},
transpileDependencies: ["vuetify"]
}
We will create the server Django project and the example app very soon. The above vue.config.js
does the following:
- The front-end is served from http://localhost:8080 during development (PROJECT_MODE=development)
- Production version (PROJECT_MODE=production) of the front-end is written into
tutorial/server/example/static
as a static build (outputDir
). We’ll create a Django app called example, which finds these static files with default configuration. webpack-stats.json
informs Django how to access the front-end assets during development and production- The front-end reloads automatically after changes
- The resulting bundle is named index. Notice that you could output multiple bundles with different TypeScript entry scripts.
Create Django back-end
Install Django for back-end.
# current directory: tutorial
# Python virtual environment at tutorial/local/venv
python3 -m venv local/venv
source local/venv/bin/activate
# Upgrade the installation tools
pip install --upgrade setuptools pip wheel
# Install Django, whitenoise is for serving static assets
pip install Django whitenoise
# Create a Django project in tutorial/server
django-admin startproject server
# Create a new Django app for your project
cd server
python manage.py startapp example
Now let’s register the new Django app by modifying the existing Django project settings tutorial/server/server/settings.py
:
# Add the following at the beginning of the file:
import os
# Add the following at the end of the file:
STATS_FILE = os.path.join(BASE_DIR, 'webpack-stats.json')
# Add the following in the INSTALLED_APPS list:
'example.apps.ExampleConfig'
# Add the following in the MIDDLEWARE list
# below 'django.middleware.security.SecurityMiddleware'
# see http://whitenoise.evans.io/en/stable/
'whitenoise.middleware.WhiteNoiseMiddleware'
Now let’s route the views from the example app in the root project. Replace the existing content in tutorial/server/server/urls.py
with the following:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('', include('example.urls')),
path('admin/', admin.site.urls)
]
We’ll create register an index page in the example app. It will serve the automatically generated Vue.js and Vuetify example page through Django. Write the following in a new file tutorial/server/example/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index')
]
Finally it’s time to create the actual view that we already registered previously.
Replace the existing content in tutorial/server/example/views.py
with the following:
import json
import pathlib
from django.shortcuts import render
from django.http import HttpResponse
from django.conf import settings
class WebpackStatsProcessing:
def __init__(self):
self.data = json.loads(pathlib.Path(settings.STATS_FILE).read_text('utf-8'))
def get_tags(self, bundle: str) -> str:
include_tags = []
for asset in self.data['chunks'][bundle]:
if asset.endswith('.map'):
continue
location = self.data['assets'][asset]['publicPath']
if asset.endswith(('.js', 'js.gz')):
include_tags.append(f'<link href="{location}" rel="preload" as="script" />')
include_tags.append(f'<script type="text/javascript" src="{location}"></script>')
if asset.endswith(('.css', '.css.gz')):
include_tags.append(f'<link href="{location}" rel="stylesheet preload" as="style" />')
return '\n'.join(include_tags)
webpack_stats_tracker = WebpackStatsProcessing()
def index(request):
return HttpResponse(render(request, 'example/vuetify_bundle.html', context={
'bundle': webpack_stats_tracker.get_tags('index'),
'title': 'Index page from Example Django app'
}))
The class WebpackStatsProcessing is an example on how to parse webpack-stats.json to load a webpack bundle (Vue.js, Vuetify, …) and output the required script and style tags for Django HTML templates. As you see in the index view, the bundle context parameter contains the required tags. The file webpack-stats.json routes the assets through the development front-end server during development and a static asset build in a production.
The only thing missing anymore is the actual Django HTML template that is rendered by the index view. First, create a new folder tutorial/server/example/templates/example
and then write the following content in a new file tutorial/server/example/templates/example/vuetify_bundle.html
<!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="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@mdi/font@5.x/css/materialdesignicons.min.css">
<title>{{ title }}</title>
</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"></div>
{{ bundle|safe }}
</body>
</html>
Run development environment
Now we are ready to run the development environment. You need two terminals at the same time:
Terminal 1 (front-end):
# current directory: tutorial/client
npm run serve
# Wait for the code compilation to finish because
# it will write the webpack-stats.json
Terminal 2 (back-end):
# current directory: tutorial
source local/venv/bin/activate
cd server
python manage.py runserver
# You can ignore any database migration errors since
# this tutorial doesn't use database models
Open http://localhost:8000 and hopefully you’ll see the front-end being served through Django with automatic reloads for front-end and back-end. For example, edit tutorial/client/src/components/HelloWorld.vue
, save changes and the front-end should update automatically. Cool. Check the page source and you should see that the assets are requested from http://localhost:8080, which is the address configured in vue.config.js
. This is possible because webpack-stats.json
instructs Django to use the http://localhost:8080 during development.
Your tutorial file structure should look something like this:
tutorial
├── client
│  ├── README.md
│  ├── babel.config.js
│  ├── node_modules
│  ├── package-lock.json
│  ├── package.json
│  ├── public
│  │  ├── favicon.ico
│  │  └── index.html
│  ├── src
│  │  ├── App.vue
│  │  ├── assets
│  │  │  ├── logo.png
│  │  │  └── logo.svg
│  │  ├── components
│  │  │  └── HelloWorld.vue
│  │  ├── main.ts
│  │  ├── plugins
│  │  │  └── vuetify.ts
│  │  ├── shims-tsx.d.ts
│  │  ├── shims-vue.d.ts
│  │  └── shims-vuetify.d.ts
│  ├── tsconfig.json
│  └── vue.config.js
├── local
│  └── venv
└── server
├── db.sqlite3
├── example
│  ├── __init__.py
│  ├── admin.py
│  ├── apps.py
│  ├── migrations
│  │  └── __init__.py
│  ├── models.py
│  ├── templates
│  │  └── example
│  │  └── vuetify_bundle.html
│  ├── tests.py
│  ├── urls.py
│  └── views.py
├── manage.py
├── server
│  ├── __init__.py
│  ├── asgi.py
│  ├── settings.py
│  ├── urls.py
│  └── wsgi.py
└── webpack-stats.json
If you use Git for version control, then remember to put the following folders and files in .gitignore:
- node_modules
- local
- static
- webpack-stats.json
Build a production version
You might want to build a production version without the front-end development server so that only Django is used. Luckily it is very easy now:
# current directory: tutorial/client
PROJECT_MODE=production npm run build
Launch the Django development server again:
python manage.py runserver
Check the page source. You should see that the assets are Django static assets instead of the earlier origin http://localhost:8080. You could deploy the Django project this way if you don’t mind the package whitenoise serving the static assets through Django and not an external CDN.
One option is to use gunicorn to serve the Django WSGI app. You can build it as a Docker image and deploy the image in production (e.g. Cloud Run in Google Cloud Platform):
FROM python:3.9.5-slim
ENV DEBIAN_FRONTEND noninteractive
ENV PYTHONUNBUFFERED True
# No need to use a root account
RUN groupadd -g 61000 docker
RUN useradd -g 61000 -l -m -s /bin/false -u 61000 docker
USER docker
COPY ./server /app
WORKDIR /app
RUN pip install --upgrade --no-cache-dir pip wheel setuptools
RUN pip install --no-cache-dir Django whitenoise gunicorn
# Add gunicorn to PATH
ENV PATH="/home/docker/.local/bin/:${PATH}"
ENV MODE="production"
CMD ["bash", "launch.sh"]
Write the following line in tutorial/server/launch.sh:
gunicorn server.wsgi:application --bind ":${PORT:-8000}" --workers 2 --threads 8 --timeout 0
Done.
Conclusion
There is still a lot of space for optimization, cleaning, configuration, making build sizes smaller and so on. However, this should get you started. Now you have Django, Vue.js, Vuetify and TypeScript living together. Happy coding!