Build a CRUD Todo app with Django and React/Redux
Tutorial for Beginners

In this tutorial, we will learn how to build a CRUD Todo application with Django REST framework for the backend, React and Redux for the frontend.
At the end of this tutorial, we will have the application that looks like this:

Table of Contents
- Setting up Django
- Setting up React
- Getting data from the API and displaying the list
- Creating Form and adding a new Todo
- Creating a Header
- Removing Todos
- Editing Todos
Setting up Django
Creating a virtual environment with Pipenv
First, we will create the folder for the project and navigate into it:
$ mkdir django-react-todo
$ cd django-react-todo
Let’s create a virtual environment by running this command:
$ pipenv --python 3
If you don’t have Pipenv installed yet, please install it by running this command:
$ pip install pipenv
We will install the packages we need:
$ pipenv install django djangorestframework
Creating a new project and some apps
In this tutorial, we will create a project named “todocrud”. We can leave out the extra folder which is automatically created by adding a dot to the end of the command and running:
$ django-admin startproject todocrud .
Next, we will create two apps. One is for the backend, the other is for the frontend:
$ python manage.py startapp todos
$ python manage.py startapp frontend
We will open the settings.py
file in the project directory, and configure to use the apps we created and Django REST framework:
We can specify the output format of a date and time by including DATETIME_FORMAT
in a configuration dictionary named REST_FRAMEWORK
.
Let’s apply migrations and start the development server:
$ python manage.py migrate
$ python manage.py runserver
Visit http://127.0.0.1:8000/ with your browser. If you see the page a rocket taking off, it worked well!
Writing backend’s modules
First, we will create a simple model. Open the models.py
file and write the following code:
Next, we will build a simple model-backed API using REST framework. Let’s create a new folder named api
and create new files __init__.py
, serializers.py
, views.py
and urls.py
in it:
todos/
api/
__init__.py
serializers.py
urls.py
views.py
Since the api
is a module, we need to include __init__.py
file.
Let’s define the API representation in the serializers.py
file:
The ModelSerializer
class will create fields that correspond to the Model fields.
Next, we will define the view behavior in the api/views.py
file:
We will finally write the URL configuration using Routers
:
We use three arguments to the register()
method, but the third argument is not required.
Writing frontend’s modules
In the frontend, all we have to do is write simple views and URL patterns.
Open the frontend/views.py
file and create the two views:
We will create the frontend/index.html
file later. Don’t worry about it now.
Add a new urls.py
file to the same directory and create the URL conf:
As you can see above, the index
view is for the index page and the TodoDetailView
is called when we request a specific object.
Wire up the URLs
We will include the frontend’s and backend’s URLs to the project’s URLconf:
Although the path for the Django admin
site is left, we are not going to use it in this tutorial.
As a final part of this chapter, we will make a new migration file and apply changes to our databases by running the commands below:
$ python manage.py makemigrations
$ python manage.py migrate
Setting up React
Creating directories
First of all, let’s create all of the directories we need:
$ mkdir -p ./frontend/src/{components,actions,reducers}
$ mkdir -p ./frontend/{static,templates}/frontend
The above command should create the directories as follows:
frontend/
src/
actions/
components/
reducers/
static/
frontend/
templates/
frontend/
Installing packages
We need to create a package.json
file by running the following command before installing packages:
$ npm init -y
In order to use npm
, Node.js needs to be installed.
Then, let’s install all the packages we use with npm
command:
$ npm i -D webpack webpack-cli
$ npm i -D babel-loader @babel/core @babel/preset-env @babel/preset-react @babel/plugin-proposal-class-properties$ npm i react react-dom react-router-dom
$ npm i redux react-redux redux-thunk redux-devtools-extension
$ npm i redux-form
$ npm i axios
$ npm i lodash
Creating config files
Add a file named .babelrc
to the root directory and configure Babel:
We can use Async/Await with Babel by writing as above.
Secondary, add a file named webpack.config.js
to the same directory and write a configuration for webpack
:
Additionally, we need to rewrite the "scripts”
property of the package.json
file:
Two new scripts have been defined. We can run scripts with npm run dev
for development or npm run build
for production. When these scripts are run, webpack
bundles modules and output the main.js
file.
Creating principal files
Let’s create three principal files and render the first word.
We will create a file named index.js
that is called first when we run the React application:
Next, we will create a file named App.js
that is a parent component:
We will finally create a template file named index.html
that is specified in the views.py
file:
In this tutorial, we will use Semantic UI as a CSS framework.
Place the wrapper for rendering the App component and the bundled script into the <body>
tag.
Checking the display
Let’s see whether it is displayed correctly.
Open another terminal and run the script:
$ npm run dev
The main.js
file should be generated in the static/frontend
directory.
Then, start the dev server and visit http://127.0.0.1:8000/:
$ python manage.py runserver
If the word “ToDoCRUD” is displayed, everything is going well so far :)
Getting data from the API and displaying the list
It is time to use Redux. We will create Actions, Reducers and Store.
Actions
Let’s define all the type properties in advance. Add a new file named types.js
into the src/actions
directory:
In order to create actions, we need to define Action Creators. Add a new file named todos.js
into the src/actions
directory:
Reducers
Reducers specify how the application’s state changes in response to actions sent to the store.
That is the role of Reducers. Add a new file named todos.js
into the src/reducers
directory and write a child reducer:
Lodash is a JavaScript utility library. It is not a requirement, but it can cut down on development time and make your codebase smaller.
Let’s create the parent reducer to put together every child reducer using combineReducers()
. Add a new file named index.js
into the src/reducers
directory:
In order to use redux-form
, we need to include its reducer in the combineReducers
function.
Store
The Store is an object to hold the state of our application. In addition, we will use the recommended middleware Redux Thunk to write async logic that interacts with the store. Let’s create a new file named store.js
in the src
directory:
Use of Redux DevTools is optional, but it is very useful because it visualizes the state changes of Redux. I will omit how to use it here, but it is highly recommended.
Components
First, create a new folder named todos
in the components
directory. And then, add a new file named TodoList.js
into the folder we created:
We use Semantic UI list to decorate the list.
The connect()
function connects this component to the store. It accepts mapStateToProps
as the first argument, Action Creators as the second argument. We will be able to use the store state as Props by specifying mapStateToProps
.
We will create a new file named Dashboard.js
in the same directory. It is just a container for TodoList
and a form we will create in the next chapter:
Open the App.js
file and update as follows:
The Provider
makes the store available to the component nested inside of it.
Checking the display
First, visit http://127.0.0.1:8000/api/todos/ and create several objects. And then, visit http://127.0.0.1:8000/.
You should see a simple list of the objects you created. Did it work?
Creating Form and adding a new Todo
Actions
Open the actions/todos.js
file, and add a new action creator:
Dispatching reset('formName')
clears our form after we submission succeeds. We will specify the form name later in the Form component.
Reducers
Open the reducers/todos.js
file, and add a new action to the reducer:
Components
Let’s create a Form component. We will create a Form separately as a reusable component so that it can also be used for editing. Create a new file named TodoForm.js
in the components/todos
directory:
The tutorial would be lengthy, so I will leave out how to use Redux Form. To understand how the Redux Form works, it is a good idea to try to customize your form referring to the documentation.
'todoForm'
is the name of this form. That is what we used in the action creator addTodo
.
When we click in the textbox and then remove the focus, it displays a validation error, so specify touchOnBlur: false
to disable it.
Next, let’s create a component for adding new todos. Create a new file named TodoCreate.js
in the components/todos
directory:
All we have to do is render the TodoForm
. By setting destroyOnUnmount
to false
, we can disable that the Redux Form automatically destroys a form state in the Redux store when the component is unmounted. It is for displaying the form state in an editing form.
If we don’t need to specify a mapStateToProps
function, set null
into connect()
.
Let’s view and test the form. Open the Dashboard.js
file, and update as follows:
Creating a Header
Let’s take a break and create a header. Create a new folder named layout
, and then add a new file name Header.js
into it:
Open the App.js
file and nest the Header
component:
In this tutorial, the header is just an ornament.
Removing Todos
First, we will create a history
object using the history package. We can use it for changing the current location. Create a new file named history.js
in the frontend/src
directory, and write the code below:
Actions
Open the actions/todos.js
file, and add two new action creators:
We have created getTodo
to get a specific object and deleteTodo
to delete the object.
We are going to create a modal window for confirmation of deletion later. The history.push('/')
method automatically takes us from the modal window to the index page after removing an object.
Reducers
Open the reducers/todos.js
file, and add the actions to the reducer:
The GET_TODO
action is the same as the ADD_TODO
action, so we only need to set the case
. For the DELETE_TODO
action, use Lodash again as a shortcut.
Components
Let’s create the modal window I just mentioned. We will have it that looks like this:

Create a new file named Modal.js
in the components/layout
directory, and write as follows:
To render the Modal
component outside the DOM hierarchy of the parent component, create a portal using createPortal()
. The first argument is the renderable child element and the second argument is the DOM element to render.
And then, open the index.html
file and add a container for the Modal inside the <body>
tag:
Next, we will create a new component TodoDelete.js
in the components/todos
directory:
The code is a bit long, but it is not so difficult. Define the helper functions that display the content and the action buttons on the modal window. Then, pass them as Props to the Modal component. onDismiss
is set to return to the index page when the dim part of the modal window is clicked.
We can retrieve the data from its own props by specifying ownProps
as the second argument to mapStateToProps
.
Let’s open the TodoList.js
file and put a delete button:
Finally, we need to configure routing using React Router. Open the App.js
file and configure as follows:
The reason for using Router
instead of BrowserRouter is explained in the REACT TRAINING document as follows:
The most common use-case for using the low-level <Router> is to synchronize a custom history with a state management lib like Redux or Mobx. Note that this is not required to use state management libs alongside React Router, it’s only for deep integration.
The exact
parameter specified in Route
returns a route only if the path
exactly matches the current URL.
That concludes this chapter. Try deleting some objects and see if it works.
Editing Todos
This is the last chapter. We are almost done, so let’s keep going!
Actions
Open the actions/todos.js
file, and add a new action creator:
Reducers
Open the reducers/todos.js
file, and add the action to the reducer:
Components
Create a new component TodoEdit.js
in the components/todos
directory:
Specify an object in initialValues
. We can get only the value of task
using the _.pick
function of Lodash. In addition, set enableReinitialize
to true
so that we can also get the value when the page is reloaded. Pass these optional properties to the TodoForm
.
Open the TodoList.js
file and update <a className='header'>{todo.task}</a>
as follows:
Let’s add the new component to the App.js
file:
Finally, let’s change the text of the button on the edit form from “Add” to “Update”. Open the TodoForm.js
file and update as follows:
Now, click on any task on the index page and try editing:

You should have been able to change the value from the form.
This tutorial ends here. The source code of this app is available on GitHub. Thank you for reading!