The Django REST Framework with Vue.js
Single-page applications in Django templates
Nested DRF serializers
In this chapter, you learn how to use the Django REST Framework to expose a REST API in your Django project, and how to serve a single-page application within Django.
Building the Billing App
Here, command_name is the name of the command you want to run. You can also make a shell function out of this command to avoid typing it again and again.
The rest of this chapter assumes you are in the repo root decoupled-dj, with the Python virtual environment active.
Building the Models
Each Invoice can have one or many ItemLines
An ItemLine belongs to exactly one Invoice
A User can have many Invoices
Each Invoice belongs to one User

The ER diagram for the billing application
billing/models.py – The Models for the Billing App
Here we take advantage of models.TextChoices, a feature shipped with Django 3.0. As for the rest, they are standard Django fields, with all the relationships set up according to the ER diagram. To add a bit more protection, since we don’t want to delete the invoices or the item lines by accident, we use PROTECT on them.
Enabling the App
decoupled_dj/settings/base.py - Enabling the Billing App
With the app in place, we are now ready to outline the interface, and later the backend code to make it work.
Wireframing the Billing App

The wireframe for the billing app
GET all or a subset of users from /billing/api/clients/
POST data for a new invoice to /billing/api/invoices/
POST for sending an email to the client (you will work on this in Chapter 11)
These interactions might sound trivial to any developer familiar with JavaScript. Nevertheless, they will help make sense of the architecture of a typical decoupled project. In the next sections, we pair a JavaScript frontend with a DRF API. Keep in mind that we focus on the interactions and on the architecture between all the moving parts rather than strive for the perfect code implementation.
We don’t have a specialized client model in this project. A client is just a user for Django. We use the term client on the frontend for convenience, while for Django everyone is a User.
Pseudo-Decoupled with the Django REST Framework
We talked about pseudo-decoupled in the previous chapter, as a way to augment the application frontend with JavaScript or to replace the static frontend altogether with a single-page app.
We haven’t touched authentication extensively yet, but in brief, one of the advantages of a pseudo-decoupled approach is that we can use the fantastic built-in Django authentication, based on sessions. In the following sections, we work in practice with one of the most popular JavaScript libraries for building interactive frontends—Vue.js—to see how it fits into Django. Vue.js is a perfect match for a pseudo-decoupled Django project, thanks to its high configurability. If you are wondering, we will cover React later in the book.
Vue.js and Django
Let’s start off with Vue. We want to serve our single-page application from within a Django template.
Vue 2.x
Babel
No routing
Linter/formatter (ESLint and Prettier)
Configuration in dedicated config files
Press Enter and let the installer configure the project. When the package manager finishes pulling in all the dependencies, take a minute to explore the Vue project structure. Once you’re done, you are ready to explore all the steps for making the single-page app work within Django.
To keep things manageable, we target only the development environment for now (we cover production in the next chapter). As anticipated in Chapter 2, Django can serve static files in development with the integrated server. When we run python manage.py runserver, Django collects all the static assets, as long as we configure STATIC_URL. In Chapter 3, we split all the settings for our project, and we configured STATIC_URL as /static/ for development. Out of the box, Django can collect static files from each app folder, and for our billing app, this means we need to put static assets in billing/static.
We serve the app with npm run serve
We serve the app through Django’s development server
With the first option, we can run and access the app at http://localhost:8081/ to see changes in real time. With the second option, is convenient to get a more real-world feeling: for example we can use the built-in authentication system. In order to go with the second option, we need to configure Vue CLI.
billing/vue_spa/vue.config.js – Vue’s Custom Configuration
Use the path specified at .env.staging as the publicPath
Put static assets to outputDir inside billing/static/billing
Put the index.html to indexPath inside billing/templates/billing
This setup respects Django expectations about where to find static files and the main template. publicPath is the path at which the Vue app is expecting to be deployed. In development/staging, we can point to /static/billing/, where Django will serve the files. In production, we provide a different path.
Django is highly configurable in regard to static files and template structure. You are free to experiment with alternative setups. Throughout the book we will adhere to the stock Django structure.
Static assets go in billing/static/billing
index.html goes in billing/templates/billing
billing/views.py - Template View for Serving the App Entry Point
If you like function view more, you can use the render() shortcut with a function view instead of a TemplateView.
billing/urls.py - URL Configuration
decoupled_dj/urls.py - Project URL Configuration

Our Vue app is served by Django’s development server
You may wonder why we use the term staging, and not development, for this setup. What you get out of this configuration, really, is more like a “pre-staging” environment where you can test the Vue app within Django. The drawback of this configuration is that to see changes reflected, we need to rebuild the Vue app every time. Of course nothing stops you from running npm run serve to start the Vue app with the integrated webpack server. In the next sections, we complete the UI for our billing app, and finally the REST backend.
Building the Vue App
Main Vue Component
Template Section of the Vue Form Component
The select for choosing the client
Two date inputs
Inputs for quantity, description, and price
A checkbox for taxed
Two buttons
JavaScript Section of the Vue Form Component
A users property inside the Vue component state
A method for handling the form submit
A mounted lifecycle method for fetching data on mount
Also, we target our API endpoints (not yet implemented): /billing/api/clients/ and /billing/api/invoices/. You can notice some fake data in users; this is so we have a minimal usable interface while we wait for building the REST API.
You can develop the frontend without a backend, with tools like Mirage JS, which can intercept and respond to HTTP calls.
When launched under Django’s umbrella, it calls /billing/api/clients/ and /billing/api/invoices/
When called with the integrated webpack server, it calls http://localhost:8000/billing/api/clients/ and http://localhost:8000/billing/api/invoices/, which are the endpoints where the DRF will listen
Development Server Configuration for Vue CLI
This ensures the project works well in staging/production with a pseudo-decoupled setup, and in development as a standalone app. In a minute, we will finally build the REST backend.
Vue.js, Django, and CSS
At this point you may wonder where CSS fits into the big picture. Our Vue component does have some classes, but we didn’t show any CSS pipeline in the previous section.
Include CSS in a base Django template
Include CSS from each single-page app
At the time of this writing, Tailwind is one of the most popular CSS libraries on the Django scene. In a pseudo-decoupled setup, you can configure Tailwind in the main Django project, include the CSS bundle in a base template, and have a single-page Vue app extend the base template. If each single-page app is independent, each one with its own style, you can configure Tailwind and friends individually. Be aware that the maintainability of the second approach might be a bit difficult in the long run.
You can find a minimal CSS implementation for the component in the source code for this chapter at https://github.com/valentinogagliardi/decoupled-dj/tree/chapter_06_decoupled_with_drf.
Building the REST Backend
/billing/api/clients/
/billing/api/invoices/
Each Invoice can have one or many ItemLines
An ItemLine belongs to exactly one Invoice
A User can have many Invoices
Each Invoice belongs to one User
The user ID to associate the invoice with
One or more item lines to associate the invoice with
Build the correct object in the frontend
Adjust the ORM logic in the DRF to save related objects
Building the Serializers
A serializer for User
A serializer for Invoice
A serializer for ItemLine
decoupled_dj/settings/base.py - Django Installed Apps with the DRF Enabled
billing/api/serializers.py – The DRF Serializers
Here we have three serializers. UserSerializer will serialize our User model. ItemLineSerializer is the serializer for an ItemLine. Finally, InvoiceSerializer will serialize our Invoice model. Each serializer subclasses the DRF’s ModelSerializer, which we encountered in Chapter 3, and has the appropriate fields mapping to the corresponding model. The last serializer in the list, InvoiceSerializer, is interesting because it contains a nested ItemLineSerializer. It’s this serializer that needs some work to comply with our frontend. To see why, let’s build the views.
Building the Views and the URL
billing/api/views.py - DRF Views
billing/urls.py - URL Patterns for the Billing API
decoupled_dj/urls.py - The Main Project URL Configuration
billing/api/serializers.py - The Serializer for an Invoice, Now with a Writable Relationship
This may be because you have a writable field on the serializer class that is not a valid argument to Invoice.objects.create(). You may need to make the field read-only or override the InvoiceSerializer.create() method to handle this correctly.
Django REST is asking us to tweak create() in InvoiceSerializer so it can accept items alongside with the invoice.
Working with Nested Serializers
billing/api/serializers.py - The Serializer for an Invoice, Now with a Customized create()
billing/models.py - The ItemLine Model with a related_name
After starting Django, you should now be able to repeat the same curl request, this time with success. At this stage, we can fix the frontend as well.
Fixing the Vue Frontend
The handleSubmit Method from the Vue Component
You should see a form that creates an invoice at http://localhost:8080/. Try to fill the form and click on Create Invoice. In the browser console, you should see the response from the Django REST Framework, with the invoice being successfully saved to the database. Great job! We finished the first real feature of this decoupled Django project.
It is a good moment to commit the changes you made so far and to push the work to your Git repo. You can find the source code for this chapter at https://github.com/valentinogagliardi/decoupled-dj/tree/chapter_06_decoupled_with_drf.
Extend the Vue component to handle multiple items for the invoice. The user should be able to click on a plus (+) button to add more items to the form, which should be sent along with the request.
Summary
This chapter paired up a Vue.js frontend with a Django REST Framework API, with Vue.js served in the same context as the main Django project.
Integrate Vue.js into Django
Interact with a DRF API from JavaScript
Work with nested serializers in the Django REST Framework
In the next chapter, we approach a more real-world scenario. We discuss security and deployment, before moving again to the JavaScript land, with Next.js in Chapter 8.