Modern Django: Part 1: Setting up Django and React
Introduction
This will be a multi part tutorial series on how to create a "Modern" web application or SPA using Django and React.js.
We will create a Note taking Single Page Application which will be rendered by ReactJS with Django as an API backend. Let's call it "ponynote", because ponies are quite popular within django community.
We will be using multiple libraries in this project, mainly, django-rest-framework
for creating APIs easily, react-router-dom
for handling in-app routing, redux
for maintaing global application state.
This part of the series is about setting up your project to serve django and react seamlessly.
The code for this repository is hosted on my github, v1k45/ponynote. You can checkout part-1
branch to all the changes done till the end of this part.
Environment setup
I'll assume that you can setup your machine to run Python3 and Node >=6 on your own or using the tutorials available on the internet.
I highly recommened you to use python virualenv so that your project dependencies do not disturb system-wide python installation. I use virtualenvwrapper to maintain multiple virtualenvs at single place using easy commands. I'll be using it throughout the tutorial.
Setup Django
After you have created and activated your virtualenv. You can start by installing django.
(ponynote) $ pip install django
After installing django, we will create a project called ponynote
using django-admin
tool.
(ponynote) $ django-admin startproject ponynote
It will generate the following project:
(ponynote) $ tree
.
└── ponynote
├── manage.py
└── ponynote
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
2 directories, 5 files
After migrating and runing development server, you will be able to see the "It worked!" welcome page of Django on http://127.0.0.1:8000.
(ponynote) $ cd ponynote
(ponynote) $ ./manage.py migrate
(ponynote) $ ./manage.py runserver
Now that we have got django working, it is time to set it up to work with React JS.
Setup ReactJS with Webpack
If you haven't heard of Webpack before, it has now become the defacto tool to for module bundling, compiling all your dependencies to small bundles (mostly one). You can learn more about webpack from their official website.
We won't be directly writing webpack configuration ourselves though, we will use create-react-app
to generate the project boilerplate with all the configurations in it. Think of create-react-app
as django-admin startproject
command but much more powerful.
For this, first we will install create-react-app
sytem-wide using npm. Because we are installing system-wide, we will need superuser permissions.
$ sudo npm install -g create-react-app
After installing we will create a react application using that command and then ejecting the config files so that we can edit them ourselves.
$ create-react-app frontend
$ cd frontend
$ npm run eject
It will create the following structure:
$ tree -I node_modules
.
├── config
│ ├── env.js
│ ├── jest
│ │ ├── cssTransform.js
│ │ └── fileTransform.js
│ ├── paths.js
│ ├── polyfills.js
│ ├── webpack.config.dev.js
│ ├── webpack.config.prod.js
│ └── webpackDevServer.config.js
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
├── README.md
├── scripts
│ ├── build.js
│ ├── start.js
│ └── test.js
└── src
├── App.css
├── App.js
├── App.test.js
├── index.css
├── index.js
├── logo.svg
└── registerServiceWorker.js
5 directories, 23 files
It is pretty obvious by the directory name which files are stored where.
You can test this application by running
$ npm run start
This will show a React welcome page.
The development server has hot loading enabled by default. This means any changes you do in your source files will be instantly reflected in the browser without you having to refresh the page manually again and again.
Integrating React and Django
Now that we have a working django server and webpack dev server running react, we will now integrate both of them so that Django can show the SPA from it's own server. We will do that using django-webpack-loader
a django application which injects link and script tag for the bundles which webpack generates dynamically.
We will start by installing webpack_loader
in our django project first:
(ponynote) $ pip install django-webpack-loader
Then in the project settings.py (ponynote.settings
) add webpack_loader
in INSTALLED_APPS
list and add the following.
WEBPACK_LOADER = {
'DEFAULT': {
'BUNDLE_DIR_NAME': 'bundles/',
'STATS_FILE': os.path.join(BASE_DIR, 'webpack-stats.dev.json'),
}
}
To serve the index page of the application we need to create a view and template in django. We'll start by creating an index template at templates/index.html
in project root. We will also need to update the project's template settings so it can detect the template directory.
In ponynote.settings.py
's TEMPLATES setting:
TEMPLATES = [
{
# ... other settings
'DIRS': [os.path.join(BASE_DIR, "templates"), ],
# ... other settings
},
]
Put the following content in your templates/index.html
:
{% load render_bundle from webpack_loader %}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<title>Ponynote</title>
</head>
<body>
<div id="root">
</div>
{% render_bundle 'main' %}
</body>
</html>
The HTML division with root
id is the mounting point of our react application. The render_bundle
tag with main
as argument renders script tag for bundle named main
which is produced by our webpack config.
Now that the template is ready, let's create a view to serve it. Since this will be a plain template, we can use django's TemplateView
directly in our url config. In your project ponynote.urls.py
file:
from django.conf.urls import url
from django.contrib import admin
from django.views.generic import TemplateView
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^', TemplateView.as_view(template_name="index.html")),
]
If you refresh your django homepage, you should see an error page with text Are you sure webpack has generated the file and the path is correct?
. This means webpack_loader
was searching for a file we specified in settings and was unable to find it.
In order to generate this file, we need to install webpack-bundle-tracker
plugin and configure webpack to generate required files. In frontend
directory, run the following commands:
$ npm install webpack-bundle-tracker --save-dev
In your frontend/config/paths.js
add the following key and value in the module.exports
object.
module.exports = {
// ... other values
statsRoot: resolveApp('../'),
}
In frontend/config/webpack.config.dev.js
change publicPath
and publicUrl
to http://localhost:3000/
.
const publicPath = 'http://localhost:3000/';
const publicUrl = 'http://localhost:3000/';
In the same file, import webpack-bundle-tracker
and include BundleTracker
in webpack plugins and replace webpackHotDevClient
line with the other two modules.
const BundleTracker = require('webpack-bundle-tracker');
module.exports = {
entry: [
// ... KEEP OTHER VALUES
// this will be found near line 30 of the file
require.resolve('webpack-dev-server/client') + '?http://localhost:3000',
require.resolve('webpack/hot/dev-server'),
// require.resolve('react-dev-utils/webpackHotDevClient'),
],
plugins: [
// this will be found near line 215-220 of the file.
// ... other plugins
new BundleTracker({path: paths.statsRoot, filename: 'webpack-stats.dev.json'}),
],
}
The reason of replacing webpackHotDevClient
, publicPath
and publicUrl
is that we are going to serve webpack dev server's bundle on a django page, and we don't want webpack hot loader to send requests to wrong url/host.
Now in frontend/config/webpackDevServer.config.js
we need to allow the server to accept requests from external origins. http://localhost:8000 (Django server) will send XHR requests to http://localhost:3000 webpack server to check for source file changes.
Put headers
object in the object returned by the exported function. This object is to be placed at the same level of https
and host
properties.
headers: {
'Access-Control-Allow-Origin': '*'
},
That's it. Stop the webpack server and run it again and go to http://localhost:8000/, you should see the same react welcome page as you did on webpack server page.
Now, any changes you do in src/App.js
file will be instantly reflected on the opened page in browser using hot loading.
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to Ponynote</h1>
</header>
<p className="App-intro">
A react app with django as a backend.
</p>
</div>
);
}
}
export default App;
This will render the following page:
Production Setup
Since this is a multi-part tutorial series, it might take some time until we reach part 4/5 where I intend to talk about deployment. I'm writing the webpack deployment guide here for people who don't want to wait until part 4 or they know react and they just need a deployable django + webpack setup.
The production setup is pretty similar to changes we did for development setup. We will edit some config values in the webpack.config
file.
Start with creating a ponynote/production_settings.py
file at the same level of your project settings.py
. For this project, the production server will use this settings file:
from .settings import *
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "assets"),
]
WEBPACK_LOADER = {
'DEFAULT': {
'BUNDLE_DIR_NAME': 'bundles/',
'STATS_FILE': os.path.join(BASE_DIR, 'webpack-stats.prod.json'),
}
}
The above config tells django to look for static files in assets
directory of project root and use webpack-stats.prod.json
as webpack stats file.
After this, create a directory assets/bundles/
in your project root. This directory will store all our static assets. The bundles sub-directory will be used as build target. Webpack will save all the build file to assets/bundles
.
$ mkdir -p assets/bundles
After this then change webpack's build output dir to assets/bundles
. In frontend/config/paths.js
change the appBuild
value:
// config after eject: we're in ./config/
module.exports = {
// .. KEEP OTHER VALUES
appBuild: resolveApp('../assets/bundles/'),
};
Now in frontend/config/webpack.config.prod.js
do the following changes:
const BundleTracker = require('webpack-bundle-tracker');
const publicPath = "/static/bundles/";
const cssFilename = 'css/[name].[contenthash:8].css';
module.exports = {
// KEEP OTHER VALUES
output: {
// NEAR LINE 67
// Generated JS file names (with nested folders).
// There will be one main bundle, and one file per asynchronous chunk.
// We don't currently advertise code splitting but Webpack supports it.
filename: 'js/[name].[chunkhash:8].js',
chunkFilename: 'js/[name].[chunkhash:8].chunk.js',
},
module: {
// .. KEEP OTHER VALUES, ONLY UPDATE THE FOLLOWING VALUES
rules: [
{
oneOf: [
// LINE 140
{
options: {
limit: 10000,
name: 'media/[name].[hash:8].[ext]',
},
},
{
// LINE 220
options: {
name: 'media/[name].[hash:8].[ext]',
},
},
],
},
],
},
plugins: [
// KEEP OTHER VALUES
// LINE 320
new BundleTracker({path: paths.statsRoot, filename: 'webpack-stats.prod.json'}),
],
}
Here we configured webpack to set static/bundles/
as publicPath
because the build files will be stored in assets/bundles
and /static/
url points to the assets
directory. We also remove static
prefixes from filenames and path to prevent unnecessary nesting of build files. This will make webpack move the whole files directory into assets/bundles
without creating an additional static
directory inside it.
After saving the file, we can build the javascript files using the following command:
$ npm run build
This will create a bunch of files in assets/bundles
and a webpack-stats.prod.json
file in the project root.
To check if everything was setup properly, we can run django server using production settings with webpack server stopped.
$ python manage.py runserver --settings=ponynote.production_settings
If you check http://localhost:8000/, you'll see the same page which was rendered when webpack server was running. If you check the source code of the webpage, you'll see the js files are now being served directly through django and not webpack.
Important points
-
It is better to build your js files on your CI server or your deployment server instead of including them in version control or source code.
-
Make sure you run
collectstatic
after youbuild
the js files, otherwise your webserver won't be able to find the build files. -
Make sure your build generates a
webpack-stats.prod.json
file. If you are deploying by building the files manually, make sure you also include it when you're copying files from your machine to server.
All the directory and filenames specified above are not enforced in any sense. Feel free to change the directory location or filenames to your liking. But make sure all config files are properly updated.
That's all on how to setup react and django to work together. In the next post we'll setup in-application routing using react-router-dom
and global state management using redux
.