Organizing your Front-End Codebase in a Django Project
The two common ways of setting up a Django / JavaScript project—understanding their advantages, shortcomings, and finding a happy middle ground.

This is Part 1 of Modern JavaScript for Django Developers.

The two common approaches of integrating Django and JavaScript

There are infinite ways to architect a Django application with a JavaScript-heavy front end. And in practice, each Django project tends to evolve into its own little Frankenstein—a unique monster brought to life by its well-meaning creator.

Frankenstein

Do you ever look at your codebase and wonder if you've created a monster? Image by Marta Simon

However, most Django projects start from one of two common archetypes, which we'll dub server-first and client-first.

Let's look at these in more detail.

The server-first architecture

A server-first architecture is usually chosen—if you can call it "choosing"—by a backend developer. It's the most common Django setup and the natural way a Django application typically evolves.

The progression to server-first goes something like this:

First you learn Django. You go through the tutorial, learn about models and views, and get your very first "hello world" application up and running. You're on a roll!

Then, inevitably, you want to do something that requires a little front-end interaction. Maybe a modal dialog, some real-time form validation, or a mobile menu. It doesn't matter.

"No problem!" you say. "I'll use JavaScript for this!"

Django makes it easy to drop JavaScript directly into your HTML templates, so you do some Googling and copy/paste some code from Stack Overflow, maybe import jQuery and a plugin or two, and you're up and running.

And it works great. Still on a roll!

jQuery

Server-first architectures—and particularly projects started prior to 2016—love using jQuery.

Then, over time, there are more modals and more menus and more pages. You start doing more complex things like managing state, dynamically rendering components, and sharing code across templates.

It starts to feel unwieldy, but you keep pressing on. A document selector here, an event callback there, maybe a few more libraries imported on various pages as you need them. The front-end code slowly expands through your Django templates and static files until it feels like JavaScript has crept its tentacles throughout the entire codebase.

At some point, your front-end code may start to feel like a spaghetti monster—a tangled mess of code and dependencies spread across your JavaScript and Django template files.

Spaghetti Monster

The Spaghetti Monster: our mascot for server-first architectures—with JavaScript spreading its tentacles throughout the codebase.

At this point you realize that you should have a better system in place. But what's not clear is what that system should be. The ever-changing, opinionated, and wildly complex world of modern JavaScript does not provide an obvious answer as to how this should be done, and especially how to do it in Django.

So you end up feeling stuck. Knowing there's a better solution out there, but not sure exactly how to achieve it. Congratulations—you've found your way to server-first Django architecture! Don't worry, you're not alone.

Problems with Server-First architectures

Like we saw above, server-first architectures typically work great at the start, but become difficult to maintain as more front-end code gets added over time.

Here are some common problems with server-first applications:

  • They may have lots of inline JavaScript code spread across various templates, making it harder to find things
  • They may have a base template with tons of included JavaScript files, increasing the page size and making it difficult to understand page-level dependencies
  • They may have front-end logic duplicated in several places instead of using shared functions and libraries
  • They may not properly manage JavaScript dependencies—both within and external to the application
  • They may not leverage the latest and greatest modern JavaScript features (e.g. ES6 or module systems)

Not every project will have all of these issues, but most will have at least some of them!

Okay, well this sounds like a bit of a mess. So let's consider a different set up entirely.

Enter, the client-first architecture.

The client-first architecture

The opposite of the server-first architecture is—naturally—client-first.

Client-first architectures have grown more and more popular with the rise of the modern JavaScript framework, and they overcome many of the problems with server-first setups.

Client-first architectures are often—though not exclusively—chosen by front-end developers. And unlike the ad-hoc path to a server-first setup described above, client-first architectures require a deliberate intention from the start of the project.

The typical path to a client-first application starts with the front end. The project's creator picks a JavaScript framework (e.g. React or Vue) on day one and sets the front end up with that framework's recommended tooling (something like create-react-app or vue create).

Meanwhile, the backend is a completely separate, standalone Django project where Django is mostly just an API into the database. Client-first projects almost always make heavy use of Django Rest Framework.

The end result looks something like this:

Client-First Architecture

A standard client-first setup. In rare cases you might even see Angular or some other framework on the front-end side.

In a client-first setup, the entire front end of the application is built inside the JavaScript framework— usually as a single-page app that handles all of the URL routing, template rendering and so on. Client-first projects often have two completely separate code repositories for the front-end and back-end code, and use a combination of Node.js and Django to serve the app.

This architecture can work great for developers who really do just want to use Django as an API—or large teams with specialized front-end and back-end developers.

However, client-first setups come with substantial tradeoffs.

The most important one is that by moving the entire UI to the JavaScript framework, Django's built-in support for templates, forms, and other front-end goodies are basically thrown out the window. In many cases these need to be rebuilt within the front-end framework.

As a result, simple tasks can become much more complicated in a client-first setup. For example, building a page to update a data model will typically involve creating a Serializer class, an API endpoint, and custom front-end code to render a form and handle validation when all you really wanted was a 5-line ModelForm.

If Django's philosophy is "batteries included" then a client-first architecture is more like BYOB: bring your own batteries.

Energizer Bunny

Our mascot for client-first architectures will be the Energizer bunny, since you'll be bringing your own batteries (and throwing out Django's).

Problems with Client-First architectures

Client-first architectures can work great. For the right project and the right team they provide a setup that is clear, organized, and scales well.

However, a client-first architecture will often come with these drawbacks:

  • They are unable to leverage a lot of Django's value: views and templates, forms, the built-in login interface, etc.
  • Implementing simple pages can be unnecessarily difficult and complex
  • They require a non-intuitive paradigm shift for classical Django developers to do anything that touches the front end
  • They don't work with many useful Django packages that haven't been designed API-first
  • They can have more complex development, build and deployment setups

As a result, development in a client-first architecture can be slower and more tedious than in a traditional server-first model—especially when you're first getting your application off the ground.

So this isn't exactly the panacea we were looking for either. Dang.

Comparing server-first and client-first set ups

Let's take stock of where we are so far. The following table summarizes some of the key differences between client-first and server-first architectures.

Server-First Client-First
Our Mascot Spaghetti Monster The Spaghetti Monster, which is what your front-end code looks like Energizer Bunny The Energizer Bunny, because you're bringing all your own batteries
Description Bits of JavaScript scattered throughout your Django templates and static files. Front end is a single page app that treats Django as a REST API.
Chosen by Back-end developers going full-stack Front-end developers going full-stack
How it is created Haphazardly over time as front-end code is needed npx create-react-app, vue createetc. with a separate, standalone Django project
Uses JavaScript Framework? Maybe. Maybe more than one! Yes, one
JavaScript Build Pipeline npm install (if using one) webpack or similar
Key Advantages Intuitive to Django developers
Leverages built-in Django features
Intuitive to JavaScript developers
Clean separation of back end and front end
Key Disadvantages Front-end code gets unwieldy
May not take advantage of latest JavaScript tooling
Forces re-implementing many Django features in the front end
Simple tasks are more difficult

So which of these do you choose?

Do you want to get the most out of Django and accept a bit of mess on the front end, or do you want to get the most out of JavaScript and sacrifice a lot of what makes Django great?

Why does this have to be a choice at all?

Well, turns out it doesn't.

That's where the hybrid architecture comes in.

Enter the hybrid architecture

The hybrid architecture is the well-constructed lasagna to your server-first front-end spaghetti. It's the batteries you always wished you had in your client-first Django back end. It's the... ok—bad analogies aside—it's the best of both worlds.

The mascot of the hybrid archictecture could be this girl, who asks simply "why not both?"

In a hybrid approach we get the best of what modern JavaScript has to offer when we want it, and we get the power, simplicity, and familiarity of Django when we don't.

The key concept is that in a hybrid architecture, rather than choosing between client-first or server-first at the project level, we choose it at the page level.

For some pages—login, static content, simple UIs—we rely primarily on a server-first setup and let Django carry the load. For other pages where we need a complex front end we lean heavily on client-first principles. And for everything in between we rely on a sane toolchain that allows us to mix and match Django with a front-end codebase that is clear and sensible to navigate.

Before getting into how to achieve this magic panacea, let's first take a look at some examples.

A hybrid architecture in practice

Here's an example of a hybrid architecture in practice from a real-world application created by the author of this guide: this site. I know, meta, right?

Remember, in a hybrid application we'll have all of the the following types of pages:

  1. Server-first Django pages with little-to-no JavaScript
  2. Client-first JavaScript pages with little-to-no Django
  3. Everything in between

Let's look at some examples of each of these.

Server-first Django pages

Pure Django pages are the simplest and most straightforward to set up since they have basically zero front-end code. In many applications these are far-and-away the most common.

Want an example of a server-first page? You're on one!

This article is written in markdown, rendered by python-markdown and Django's template engine for your reading pleasure.

In addition to the guides—most of this site fits into the server-first category, including the landing page, user sign up flow, and most of what you see post-login.

Pegasus Sign Up

Sign up and login pages are one of the best places to leverage Django's built in functionality since they work out of the box with almost zero additional code. Building the same page in a client-first setup takes substantially more code and a third-party library.

It's quite common for server-first pages to dominate any particular web application. They're fast, simple, easy to develop, and often all that's needed.

By using a server-first architecture for these basic components, you can increase the speed of development and ease of maintenance of your project and let Django to do what it's best at—create rock-solid web applications without reinventing the wheel.

Basecamp recently made waves by revealing that hey.com consisted primarily of server-first pages.

Client-first JavaScript pages

Still, not everything fits into our server-first world—if it did, this entire series wouldn't need to exist!

So let's now cover one of those pages where we want a more complex, responsive UI built using a JavaScript framework. A client-first page.

Our client-first pages adopt a very similar setup to the client-first architecture described above, only at the page level.

So next we'll walk through an example client-first page hosted on this site—a basic page that allows you to create, update, and delete a sample data model of employees.

In order to access the example, you'll need to first create an account on this site. This is because the example uses data associated with your Django User object. Don't worry—if you'd rather not do this, you can still follow along with the screenshots!

To access the demo, first create an account and then then visit this link. The demo is a client-first page made with React. There is also a Vue version. You won't be able to tell the difference, since they do the exact same thing.

When you first load the page you see the following:

Empty Object Demo

However, clicking "Create Employee" doesn't actually take you to a new page but instead immediately renders a form where you can enter employee details.

Object Demo - Add Employee

Again, clicking "Add Employee" immediately takes you back to a list view, where you can see the newly created employee and more options.

Object Demo - Employee List

You can keep adding, updating and deleting employees, and note how fast and dynamic the experience feels. That's modern JavaScript working for you, and the basics of a single-page app!

Behind the scenes, on this page Django is basically rendering the following template and letting React/Vue take over from there.

<div id="js-framework-home"></div>

By using a client-first architecture in this example, we can leverage JavaScript to create a highly complex, dynamic, stateful single-page application that suits the needs of the page.

There are a lot of details we're glossing over for now—including exactly how Django hands things off to the front end and how information flows between the two sides. Don't worry, we'll come back to these later!

One other thing you might have noticed is that the object demo sits inside the global layout and navigation of the larger site. This is another benefit of hybrid applications—you can use your existing Django template hierarchy and still make single page apps inside it.

The in-between pages

Not everything is as clear-cut as "only Django" and "only JavaScript". In practice, most pages are somewhere in between—content rendered by Django but with varying levels of front-end code. These are the pages that first got us into trouble with our original server-first setup.

One example of this you might find in a SaaS application is a subscription page—where a customer can sign up to a subscription on your site using Stripe—a payment gateway. The subscription demo page is available without an account so if you skipped setting one up above you can still follow along.

Subscription Page

A demo subscription page—which allows users to sign up for a subscription on our site.

This page looks simple, but there's a fair amount of front-end code trickling in.

For example, clicking on a plan needs to move the highlighted blue box and check mark, as well as update the price at the bottom. Likewise, toggling between "Annual" and "Monthly" updates all the prices as well as the text below it. And of course, the Stripe-managed credit card form needs to handle a payment flow via Stripe's libraries.

In case you're wondering what this site is all about, it's a demo of SaaS Pegasus—the Django SaaS Boilerplate.
Pegasus is the fastest and easiest way to launch a new project with Django.

These in-between pages can be tricky to deal with. If we're not careful we can easily end up back in the "spaghetti monster" server-first world.

So a key part of our hybrid architecture will be coming up with a way to manage hybrid pages like this one in a way that doesn't lead to front-end spaghetti. We'll cover all of that in depth in Parts 3 and 4.

However, before we get there we're first going to need to get some modern JavaScript fundamentals in place.

Read on in Part 2: A Crash Course in Modern JavaScript Tooling. Don't worry—it's not as bad as it sounds.

Subscribe for Updates

Sign up to get notified when I publish new articles about building SaaS applications with Django.

I don't spam and you can unsubscribe anytime.