--- title: "Combine Django, Vue.js, Vuetify and TypeScript" date: 2021-06-27T09:18:41+03:00 draft: false --- 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](https://www.djangoproject.com/) got you covered for web development, from excellent database tools to user authentication, and everything between * [Vue.js](https://vuejs.org/) makes it easy to write interactive user interfaces * [Vuetify](https://vuetifyjs.com/en/) gives you a toolbox of graphical user interface components that follow the Material Design-framework * [TypeScript](https://www.typescriptlang.org/) 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](https://flask.palletsprojects.com/en/2.0.x/) or other Python web frameworks instead of Django. Actually, you could use any web framework, including [Actix Web](https://actix.rs/) for Rust, since the same principles apply for connecting the front-end with the back-end. * This tutorial uses [whitenoise](http://whitenoise.evans.io/en/stable/) with Django to serve static assets in production builds. You might want to switch to a [CDN](https://en.wikipedia.org/wiki/Content_delivery_network) 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. ```bash # 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. ```bash # 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: ```js 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. ```bash # 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`: ```python # 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: ```python 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` ```python 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: ```python 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'') include_tags.append(f'') if asset.endswith(('.css', '.css.gz')): include_tags.append(f'') 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` ```html {{ title }}
{{ bundle|safe }} ``` ## Run development environment Now we are ready to run the development environment. You need two terminals at the same time: Terminal 1 (front-end): ```bash # 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): ```bash # 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](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: ```bash # current directory: tutorial/client PROJECT_MODE=production npm run build ``` Launch the Django development server again: ```bash 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): ```docker 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: ```bash 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!