Creating websites using React and Django REST Framework

Written by mark.winter | Published 2017/07/25
Tech Story Tags: react | django | django-rest-framework | web-development

TLDRvia the TL;DR App

Lately at work our go to architecture for creating websites is to use a React frontend with a Django REST Framework (DRF) backend. The two are connected by API calls using axios in the frontend. Some Redux is used as well for storing global app state. This is our preferred method as it allows the frontend and backend to be completely decoupled. And as long as we define a list of endpoints and returned data to work with, the frontend and backend can be developed in parallel. This also allows us the option to easily create mobile apps for any of the projects in the future as they can just consume the backend API. On a side note, we’re currently looking at using React Native for future mobile app projects.

In the rest of this post, I’ll go through how to setup a React frontend and DRF backend project. Note I assume you’re already familiar with React, Redux, Django, DRF, npm etc. This isn’t a tutorial for them.

The Backend

There isn’t much to do for the default backend outside of simply installing Django and DRF, and setting up the database. From the root of your project folder, create a virtualenv and install Django and DRF.

$ virtualenv env$ source env/bin/activate$ pip3 install django djangorestframework$ pip3 freeze > requirements.txt

Now start a new Django project and Django app.

$ django-admin startproject backend$ cd backend$ django-admin startapp api

You should now setup your database and edit your project’s settings to use the database. Good documentation on how to do this for your particular DB can be found on Django’s website. Then you should configure DRF following the instructions on their website found here.

The next step you will most likely want to do is setup authentication in your API. If you wont require authentication (e.g. no user logins), you can skip this. My company’s React/Django template project currently uses plain token authentication as it’s the simplest to setup. I recommend this for those learning as well, but it’s not the best for production. These tokens never expire which poses quite a security risk if it’s ever leaked. Soon we’ll update the template project to use something like oauth, or expiring JWT tokens — as of yet it’s undecided. Documentation for configuring token authentication is here.

Once token authentication is configured, you will want to create a urls.py in your app (if you haven’t already), and use DRF’s token auth view. This endpoint at /auth lets users POST their username and password and get their auth token as a response. In the frontend, this token will get stored in the Redux store for further API calls.

# file: api/urls.py

from django.conf.urls import urlfrom rest_framework.authtoken import views as drf_views

urlpatterns = [url(r'^auth$', drf_views.obtain_auth_token, name='auth'),]

And just to make sure it’s clear, your backend/urls.py file should now look like this

# file: backend/urls.py

from django.conf.urls import url, include

urlpatterns = [url(r'^', include('api.urls', namespace='api', app_name='api')),]

By doing this, we’re just making each app look after its own urls. Maybe in the future you will add more apps to the backend and it would get messy to add everything to backend/urls.py

You should now have a functioning backend DRF API with a single endpoint /auth that lets users get their auth token. Let’s setup a user and run the backend server for testing later.

$ python3 manage.py migrate$ python3 manage.py createsuperuser$ python3 manage.py runserver 0.0.0.0:8000

Remember to run migrate for the first time to create your database. Then we’ll create a user for whom we can get an auth token for. With the server now running, you can test your /auth endpoint works quickly using curl

$ curl -X POST -d "username=username&password=password" http://localhost:8000/auth

The Frontend

For the frontend, we used Facebook’s create-react-app as the starting point. So the first thing to do is install it and create a new project using it in the root of your project folder. We also eject the configuration as we need more control, and everyone on our team is fine with using webpack etc.

$ npm install -g create-react-app$ create-react-app frontend$ cd frontend$ npm run eject

Next we want to install some additional dependencies.

$ npm install --save-dev babel-preset-es2015 babel-preset-stage-3$ npm install --save redux redux-logger redux-persist react-redux$ npm install --save axios react-router-dom lodash

Now instead of listing all the code used by our React template project, I’m just going to show the important parts that connect our frontend to our backend. Firstly create a redux store as we will want to save the user’s auth token for making more API calls in the future

// file: src/store.jsimport { compose, createStore, applyMiddleware } from 'redux';import { createLogger } from 'redux-logger';import { persistStore, autoRehydrate } from 'redux-persist';import rootReducer from './reducers';

const store = createStore(rootReducer,compose(applyMiddleware(createLogger(),),autoRehydrate()));persistStore(store);export default store;

And then setup the token reducer

// file: src/reducers/index.jsimport { combineReducers } from 'redux';import * as actionType from '../actions/types';

const tokenInitialState = null;const token = (state = tokenInitialState, action) => {switch(action.type) {case actionType.SET_TOKEN:return action.data;default:return state;}}

const appReducer = combineReducers({token,})

const rootReducer = (state, action) => {return appReducer(state, action);}

export default rootReducer;

And finally the actions (notice this is two files in one code block)

// file: src/actions/index.jsimport * as actionType from './types';

export const setToken = (data) => {return {type: actionType.SET_TOKEN,data}}

// file: src/actions/types.jsexport const SET_TOKEN = "SET_TOKEN";

We now have an action we can dispatch to store the user’s token after logging in. So next lets look at how we login

// file: src/util/Auth.jsimport axios from 'axios';import _ from 'lodash';import store from '../store';import { setToken } from '../actions'import { URL, LOGIN } from '../config/Api';

export function InvalidCredentialsException(message) {this.message = message;this.name = 'InvalidCredentialsException';}

export function login(username, password) {return axios.post(URL + LOGIN, {username,password}).then(function (response) {store.dispatch(setToken(response.data.token));}).catch(function (error) {// raise different exception if due to invalid credentialsif (_.get(error, 'response.status') === 400) {throw new InvalidCredentialsException(error);}throw error;});}

export function loggedIn() {return store.getState().token !== null;}

This piece of code uses axios to post to our /auth backend and then dispatch the returned token to our redux store. Once this is done, we can now create an axios based API client using our stored token to make further API calls from elsewhere in our React components.

// file: src/util/ApiClient.jsimport axios from 'axios';import store from '../store';import { URL } from '../config/Api';

export const apiClient = function() {const token = store.getState().token;const params = {baseURL: URL,headers: {'Authorization': 'Token ' + token}};return axios.create(params);}

We reference the file ../config/Api in the last two code-blocks. Here’s what that file looks like — it’s simply a file to map constants to endpoints, making the code more readable and easier to modify later.

export const URL = process.env.API_URL;export const LOGIN = "/auth";

That’s all there is to it to connect our frontend to our backend. You can now try using the Auth.js login function to get the auth token for the user we created earlier. If you do, you can look at your browser’s dev tools to check the output from redux-logger to see the result of the setToken redux action.

I would love to hear some feedback from others about this setup: how could I potentially improve it, anything that needs changing or could be done better, etc.


Published by HackerNoon on 2017/07/25