You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Guide on how to create and set up a Dockerized web app using Django REST APIs and ReactJS
Hopefully this will answer "How do I setup or start a Django project using REST Framework and ReactJS?"
This is a guide to show you step by step how this can be setup. If you just want to get started, use the cookiecuter I set up cookiecutter-django-reactjs. It basically is a fork of pydanny's cookiecutter, just added the front-end stuff :).
I created this because it was SUCH a pain in the ass setting up a project using all the latest technologies.
After some research, I figured it out and have it working. The repo that implements this is located here. Feel free to use it as a boilerplate ;)
Main features:
Django REST APIs
ReactJS with Redux Pattern
Webpack module bundler manager
Hot Reloading of ReactJS components for quicker development
EditorConfig helps developers define and maintain consistent coding styles between different editors and IDEs. This file will initialize the coding style for this project.
root = true
# Unix-style newlines with a newline ending every file
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
# Python settings
[*.py]
line_length=120
indent_style = space
indent_size = 4
# JavaScript, JSX, CSS, SCSS, HTML settings
[*.{js,jsx,css,scss,html}]
indent_style = space
indent_size = 2
# Markdown settings
[*.md]
trim_trailing_whitespace = false
# Config files
[*.conf]
indent_style = space
indent_size = 2
Flake8
Flake8 is a is a wrapper around these tools: PyFlakes. pep8. It is a linting utility for Python. When used together with Atom, Atom Linter Plugin and Atom Flake8 Plugin, this will mark source code that doesn't conform to settings in .flake8 file.
Example .flake8 file:
[flake8]
max-line-length = 125
ESLint
The pluggable linting utility (tool to verify code quality) for JavaScript and JSX. When used together with Atom, Atom Linter Plugin and Atom ESLint Plugin, this will mark mark source code that doesn't conform to settings in .eslintrc file.
On MacOSX we can use the Postgress App. Just download and move the app into your Applications folder. Follow the instructions, its the easiest way I have seen to set up PostgresSQL!
Create an optimized user for our Django projects
Login to the database from the terminal using:
psql
CREATEUSERdjango WITH PASSWORD '[password]';
ALTER ROLE django SET client_encoding TO 'utf8';
ALTER ROLE django SET default_transaction_isolation TO 'read committed';
ALTER ROLE django SET timezone TO 'UTC';
ALTERUSER django CREATEDB;
Create a database for this specific Django project
Login to the database from the terminal using:
psql
CREATE DATABASE [project-name];
GRANT ALL PRIVILEGES ON DATABASE [project-name] TO django;
It’s common practice to use a virtual environment for your Python projects in order to create self-contained development environments. The goal of pyenv is to prevent different versions of Python and libraries/packages from messing with each other. It’s like an isolated, soundproof room within your home where you can scream as loud as you want, about anything you want, and nobody else outside that room can hear it.
Use pyenv-virtualenv to create a virtual environment for your project:
Create Python requirements and install local dependencies
Python requirements folder
mkdir requirements
Base Python requirements - ./requirements/base.txt
# Django
django==1.10.4
# Configuration
dj-database-url==0.4.1
python-decouple==3.0
whitenoise==3.2.2
# Django 3rd party modules
django-allauth==0.30.0
django-extensions==1.7.5
django-model-utils==2.6
django-rest-auth==0.9.0
django-webpack-loader==0.4.1
djangorestframework==3.5.3
djangorestframework-jwt==1.9.0
# Python-PostgreSQL Database Adapter
psycopg2==2.6.2
# Time zones support
pytz==2016.10
Local environment Python requirements - ./requirements/local.txt
# Import all base requirements
-r base.txt
# Django debug toolbar
django-debug-toolbar==1.6
# Code linter
flake8==3.2.1
Production environment Python requirements - ./requirements/production.txt
# Import all base requirements
# Pro-tip: Try not to put anything here. Avoid dependencies in production that aren't in development.
-r base.txt
# WSGI Handler
gunicorn==19.6.0
# Email backend
django-anymail==0.7
I always start Django projects the same way, just so it can create the initial folder as django_config. This is just personal perference on how I like to structure Django projects.
{% load staticfiles %}
<htmlclass="{% block html_class %}{% endblock html_class %}" lang="en"><head><!-- Allows you to inject head elements here -->
{% block head %}
<metacharset="utf-8"><metahttp-equiv="X-UA-Compatible" content="IE=edge"><metaname="viewport" content="width=device-width, initial-scale=1"><!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags --><metaname="author" content=""><metaname="description" content=""><metaname="keywords" content=""><title>{% block head_title %}{% endblock head_title %}</title><!-- Allows you to inject CCS here -->
{% block stylesheets %}
<!-- Third-party CSS libraries go here --><!-- Latest compiled and minified Bootstrap CSS --><linkrel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"><!-- Optional Bootstrap theme --><linkrel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous"><!-- FontAwesome http://fontawesome.io/ --><linkhref="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-T8Gy5hrqNKT+hzMclPo118YTQO6cYprQmhrYwIiQ/3axmI1hQomh7Ud2hPOy8SP1" crossorigin="anonymous"><!-- Animate CSS https://github.com/daneden/animate.css --><linkrel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.5.2/animate.min.css"><!-- Project specific Stylesheets -->
{% endblock stylesheets %}
<!-- Title Icon --><!-- <link rel="shortcut icon" href=""> -->
{% endblock head %}
</head><bodyclass="{% block body_class %}{% endblock body_class %}"><!-- Allows you to inject body content here -->
{% block body %}
{% endblock body %}
<!-- Project specific JavaScript --><!-- Allows you to inject JavaScript here -->
{% block javascript %}
{% endblock javascript %}
<!-- Google Analytics goes here -->
{% block google_analytics %}
{% endblock google_analytics %}
</body></html>
Create the index.html template:
{% extends 'base.html' %}
{% load staticfiles %}
{% block body %}
<h1>Hello World</h1>
{% endblock body %}
Update URLs to include Django Debug toolbar and the new index template
Webpack is module bundler. It bundles all JavaScript, JSX, etc. code for our project and manages our codebase to be split into bundles to be loaded in in our different environments.
This also includes some goodies like hot-reloading ;). This is extremely convienent when developing React components.
Create our Webpack directory
mkdir webpack
Create our base configuration for Webpack - webpack.base.config.js
NOTE: I have made some changes to this. I wanted to include the redux-dev-tools library. Refer to this commit to see the changes.
In this step, we will make sure Django and ReactJS are communicating. We will now be able to use Django as our backend server and ReactJS to handle all of the frontend.
Write placeholder React Component
Write a simple React component as a placeholder for now.
mkdir -p static/js/src/main/components/
./static/js/src/main/components/hello-world.jsx should look like this:
We need to tell the Django template to load the latest javascript bundle. Because we are using Django webpack loader, we can enable hot reloading during development ;).
The index.html page should now look like this:
Setup Karma and Mocha for running JavaScript Unittests
Karma is a spectular test runner for JavaScript. Karma will allow a productive testing environment, one where we can just write the code and get instant feedback from our tests.
varwebpackConfig=require('./webpack.local.config.js');webpackConfig.entry={};module.exports=function(config){config.set({// base path that will be used to resolve all patterns (eg. files, exclude)basePath: '',// frameworks to use// available frameworks: https://npmjs.org/browse/keyword/karma-adapterframeworks: ['mocha'],// list of files / patterns to load in the browserfiles: ['../static/js/src/test_index.js'],// list of files to excludeexclude: [],// preprocess matching files before serving them to the browser// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessorpreprocessors: {'../static/js/src/test_index.js': ['webpack','sourcemap'],},// test results reporter to use// possible values: 'dots', 'progress'// available reporters: https://npmjs.org/browse/keyword/karma-reporterreporters: ['progress'],// web server portport: 9876,// enable / disable colors in the output (reporters and logs)colors: true,// level of logging// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUGlogLevel: config.LOG_INFO,// enable / disable watching file and executing tests whenever any file changesautoWatch: true,autoWatchBatchDelay: 300,// start these browsers// available browser launchers: https://npmjs.org/browse/keyword/karma-launcherbrowsers: ['Chrome'],// Continuous Integration mode// if true, Karma captures browsers, runs the tests and exitssingleRun: false,// Concurrency level// how many browser should be started simultaneousconcurrency: Infinity,// Webpackwebpack: webpackConfig,webpackServer: {noInfo: true}});};
Setup testing structure for our project
We set this up with an example test because if not Karma will complain ;)
Create docker_compose/django/entrypoint.sh file. This will make sure we can connect to PostgreSQL from Django server:
#!/bin/bashset -e
cmd="$@"# This entrypoint is used to play nicely with the current cookiecutter configuration.# Since docker-compose relies heavily on environment variables itself for configuration, we'd have to define multiple# environment variables just to support cookiecutter out of the box. That makes no sense, so this little entrypoint# does all this for us.# the official postgres image uses 'postgres' as default user if not set explictly.if [ -z"$POSTGRES_USER" ];thenexport POSTGRES_USER=postgres
fi# If not DB is set, then use USER by defaultif [ -z"$POSTGRES_DB" ];thenexport POSTGRES_DB=$POSTGRES_USERfi# Need to update the DATABASE_URL if using DOCKERexport DATABASE_URL=postgres://$POSTGRES_USER:$POSTGRES_PASSWORD@postgres:5432/$POSTGRES_DBfunctionpostgres_ready(){
python <<ENDimport sysimport psycopg2try: conn = psycopg2.connect(dbname="$POSTGRES_DB", user="$POSTGRES_USER", password="$POSTGRES_PASSWORD", host="postgres")except psycopg2.OperationalError: sys.exit(-1)sys.exit(0)END
}
until postgres_ready;do>&2echo"Postgres is unavailable - sleeping"
sleep 1
done>&2echo"Postgres is up - continuing..."exec$cmd
FROM node:latest
COPY . /app
WORKDIR /app
RUN npm install
COPY ./docker_compose/node/development/start.sh /start.sh
RUN sed -i 's/\r//' /start.sh
RUN chmod +x /start.sh
Create docker_compose/node/development/start.sh file. This will run the development server:
Helpful utilities for dealing with Django APIs with React
We will create helpful utilities to consume APIsin a secure fashion. We will use the following libraries:
react-cookie
axios
Front-end utilities
1. Create utilities directory
mkdir -p static/js/src/utilities/
2. Create script to interact with application cookie
We will use the react-cookie library (Makes this so easy!). We will use Cross-Site Request Forgery (CSRF) token and JSON web token (JWT) to secure our application.
It will make our application safe against Cross-site scripting (XSS) and CSRF attacks by following these strategies:
Store JWT in a httpOnly and secure cookie
Use a CSRF token with any methods that alter data on the server (PUT, POST, DELETE, PATCH).
GET should NOT cause changes server side. This is reserved for PUT, POST, DELETE, PATCH methods.
Create the static/js/src/utilities/cookie.js file. It should look like this:
importcookiesfrom'react-cookie';// This is the place where we can load elements from a cookie to be used in our app// Django CRSF Token is stored in a cookieconstcsrftoken=cookies.load('csrftoken');// JWT is going to be saved into cookie// cookies.save('jwt', response.data.token, { secure: true, httpOnly: true });// Therefore it will automatically be sent in the header of all API requests// JWT will NOT be accessible to JavaScript because it is httpOnly :)export{csrftoken};
3. Create script to use to interact with Django APIs
We will use the axios library to set this up. Create the static/js/src/utilities/api.js file. It should look like this:
importaxiosfrom'axios';import*ascookiefrom'./cookie';constapi=axios.create({baseURL: process.env.BASE_URL,timeout: 1000,});// CRSF token is needed in all requests that can make a change server sideapi.defaults.headers.post['X-CSRFToken']=cookie.csrftoken;api.defaults.headers.put['X-CSRFToken']=cookie.csrftoken;api.defaults.headers.patch['X-CSRFToken']=cookie.csrftoken;// api.defaults.headers.delete['X-CSRFToken'] = cookie.csrftoken; // Currently axios can't set headers for DELETE// Since we will only be using JSON APIs, add Content-Type: application/json to header as defaultapi.defaults.headers.post['Content-Type']='application/json';api.defaults.headers.put['Content-Type']='application/json';api.defaults.headers.patch['Content-Type']='application/json';// Since we will only be using JSON APIs, add Accept: application/json to header as defaultapi.defaults.headers.get.Accept='application/json';api.defaults.headers.post.Accept='application/json';api.defaults.headers.put.Accept='application/json';api.defaults.headers.patch.Accept='application/json';// JWT is going to be saved into cookie// cookies.save('jwt', response.data.token, { secure: true, httpOnly: true });// Therefore it will automatically be sent in the header of all API requests// JWT will not be accessible to JavaScript because it is httpOnly :)exportdefaultapi;
NOTE: Currently axios does not allow setting headers on DELETE method. I raised this issue and it should be included in the next release.