Tutorial

How To Build a To-Do application Using Django and React

Updated on February 18, 2021
author

Jordan Irabor

How To Build a To-Do application Using Django and React

Introduction

In this tutorial, you will build a To-Do application using Django and React.

React is a JavaScript framework for developing SPAs (single-page applications). It has solid documentation and a vibrant ecosystem around it.

Django is a Python web framework that simplifies common practices in web development. Django is reliable and also has a vibrant ecosystem of stable libraries supporting common development needs.

For this application, React serves as the frontend, or client-side framework, handling the user interface and getting and setting data via requests to the Django backend, which is an API built using the Django REST framework (DRF).

At the end of this tutorial, you will have a fully working application:

Animated gif of a user interacting with the application to create a new task and toggle it between complete and incomplete statuses.

If you want to deploy the app in this tutorial, you can deploy directly from a GitHub repo using DigitalOcean App Platform.

This application will allow users to create tasks and mark them as complete or incomplete.

Prerequisites

To follow along with this tutorial, you will need to:

  1. Install and set up a local programming environment for Python 3
  2. Install Node.js and Create a Local Development Environment

This tutorial was verified with Python v3.9.1, pip v20.2.4, Django v3.1.6, djangorestframework v3.12.2, django-cors-headers v3.7.0, Node v15.8.0, npm v7.5.4, React v17.0.1, and axios v0.21.0.

Step 1 — Setting Up the Backend

In this section, you will create a new project directory and install Django.

Open a new terminal window and run the following command to create a new project directory:

  1. mkdir django-todo-react

Next, navigate into the directory:

  1. cd django-todo-react

Now install Pipenv using pip:

  1. pip install pipenv

Note: Depending on your installation, you may need to use pip3 instead of pip.

And activate a new virtual environment:

  1. pipenv shell

Install Django using Pipenv:

  1. pipenv install django

Then create a new project called backend:

  1. django-admin startproject backend

Next, navigate into the newly created backend directory:

  1. cd backend

Start a new application called todo:

python manage.py startapp todo

Run migrations:

python manage.py migrate

And start up the server:

python manage.py runserver

Navigate to http://localhost:8000 in your web browser:

Screenshot of the default Django application running successfully.

At this point, you will see an instance of a Django application running successfully. Once you are finished, you can stop the server (CONTROL+C or CTRL+C).

Registering the todo Application

Now that you have completed the setup for the backend, you can begin registering the todo application as an installed app so that Django can recognize it.

Open the backend/settings.py file in your code editor and add todo to the INSTALLED_APPS:

backend/settings.py
# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'todo',
]

Then, save your changes.

Defining the Todo Model

Let’s create a model to define how the Todo items should be stored in the database.

Open the todo/models.py file in your code editor and add the following lines of code:

todo/models.py
from django.db import models

# Create your models here.

class Todo(models.Model):
    title = models.CharField(max_length=120)
    description = models.TextField()
    completed = models.BooleanField(default=False)

    def _str_(self):
        return self.title

The code snippet above describes three properties on the Todo model:

  • title
  • description
  • completed

The completed property is the status of a task. A task will either be completed or not completed at any time. Because you have created a Todo model, you will need to create a migration file:

  1. python manage.py makemigrations todo

And apply the changes to the database:

  1. python manage.py migrate todo

You can test to see that CRUD operations work on the Todo model you created by using the admin interface that Django provides by default.

Open the todo/admin.py file with your code editor and add the following lines of code:

todo/admin.py
from django.contrib import admin
from .models import Todo

class TodoAdmin(admin.ModelAdmin):
    list_display = ('title', 'description', 'completed')

# Register your models here.

admin.site.register(Todo, TodoAdmin)

Then, save your changes.

You will need to create a “superuser” account to access the admin interface. Run the following command in your terminal:

  1. python manage.py createsuperuser

You will be prompted to enter a username, email, and password for the superuser. Be sure to enter details that you can remember because you will need them to log in to the admin dashboard.

Start the server once again:

  1. python manage.py runserver

Navigate to http://localhost:8000/admin in your web browser. And log in with the username and password that was created earlier:

Screenshot of the admin interface for the Django application.

You can create, edit, and, delete Todo items using this interface:

Screenshot of the admin interface for the Django application displaying todo items.

After experimenting with this interface, you can stop the server (CONTROL+C or CTRL+C).

Step 2 — Setting Up the APIs

In this section, you will create an API using the Django REST framework.

Install the djangorestframework and django-cors-headers using Pipenv:

  1. pipenv install djangorestframework django-cors-headers

You need to add rest_framework and corsheaders to the list of installed applications. Open the backend/settings.py file in your code editor and update the INSTALLED_APPS and MIDDLEWARE sections:

backend/settings.py
# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'corsheaders',
    'rest_framework',
    'todo',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'corsheaders.middleware.CorsMiddleware',
]

Then, add these lines of code to the bottom of the backend/settings.py file:

backend/settings.py
CORS_ORIGIN_WHITELIST = [
     'http://localhost:3000'
]

django-cors-headers is a Python library that will prevent the errors that you would normally get due to CORS rules. In the CORS_ORIGIN_WHITELIST code, you whitelisted localhost:3000 because you want the frontend (which will be served on that port) of the application to interact with the API.

Creating serializers

You will need serializers to convert model instances to JSON so that the frontend can work with the received data.

Create a todo/serializers.py file with your code editor. Open the serializers.py file and update it with the following lines of code:

todo/serializers.py
from rest_framework import serializers
from .models import Todo

class TodoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Todo
        fields = ('id', 'title', 'description', 'completed')

This code specifies the model to work with and the fields to be converted to JSON.

Creating the View

You will need to create a TodoView class in the todo/views.py file.

Open the todo/views.py file with your code editor and add the following lines of code:

todo/views.py
from django.shortcuts import render
from rest_framework import viewsets
from .serializers import TodoSerializer
from .models import Todo

# Create your views here.

class TodoView(viewsets.ModelViewSet):
    serializer_class = TodoSerializer
    queryset = Todo.objects.all()

The viewsets base class provides the implementation for CRUD operations by default. This code specifies the serializer_class and the queryset.

Open the backend/urls.py file with your code editor and replace the contents with the following lines of code:

backend/urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework import routers
from todo import views

router = routers.DefaultRouter()
router.register(r'todos', views.TodoView, 'todo')

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include(router.urls)),
]

This code specifies the URL path for the API. This was the final step that completes the building of the API.

You can now perform CRUD operations on the Todo model. The router class allows you to make the following queries:

  • /todos/ - returns a list of all the Todo items. CREATE and READ operations can be performed here.
  • /todos/id - returns a single Todo item using the id primary key. UPDATE and DELETE operations can be performed here.

Let’s restart the server:

  1. python manage.py runserver

Navigate to http://localhost:8000/api/todos in your web browser:

Screenshot of the API results for the Todo items.

You can CREATE a new Todo item using the interface:

Screenshot of the API tool for creating new Todo items.

If the Todo item is created successfully, you will be presented with a successful response:

Screenshot of the API response for a successful Todo item creation.

You can also perform DELETE and UPDATE operations on specific Todo items using the id primary keys. Use the address structure /api/todos/{id} and provide an id.

Add 1 to the URL to examine the Todo item with the id of “1”. Navigate to http://localhost:8000/api/todos/1 in your web browser:

Screenshot of API tools for DELETE and PUT.

This completes the building of the backend of the application.

Step 3 — Setting Up the Frontend

Now that you have the backend of the application complete, you can create the frontend and have it communicate with the backend over the interface that you created.

First, open a new terminal window and navigate to the django-todo-react project directory.

To set up the frontend, this tutorial will rely upon Create React App. There are several approaches to using create-react-app. One approach is to use npx to run the package and create the project:

  1. npx create-react-app frontend

You can learn more about this approach by reading the How To Set Up a React Project with Create React App.

After the project is created, you can change into the newly created frontend directory:

  1. cd frontend

Then, start the application:

  1. npm start

Your web browser will open http://localhost:3000 and you will be presented with the default Create React App screen:

Screenshot of landing page for the default Create React App application.

Next, install bootstrap and reactstrap to provide user interface tools.

  1. npm install bootstrap@4.6.0 reactstrap@8.9.0 --legacy-peer-deps

Note: You may encounter unable to resolve dependency tree errors depending on your versions of React, Bootstrap, and Reactstrap.

At the time of the revision, the latest version of popper.js has been deprecated and will conflict with React 17+. This is a known issue and it is possible to use the --legacy-peer-deps option when installing.

Open index.js in your code editor and add bootstrap.min.css:

frontend/src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.css';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

If you are having difficulty with this step, you can consult the official documentation for adding bootstrap.

Open App.js in your code editor and add the following lines of code:

frontend/src/App.js
import React, { Component } from "react";

const todoItems = [
  {
    id: 1,
    title: "Go to Market",
    description: "Buy ingredients to prepare dinner",
    completed: true,
  },
  {
    id: 2,
    title: "Study",
    description: "Read Algebra and History textbook for the upcoming test",
    completed: false,
  },
  {
    id: 3,
    title: "Sammy's books",
    description: "Go to library to return Sammy's books",
    completed: true,
  },
  {
    id: 4,
    title: "Article",
    description: "Write article on how to use Django with React",
    completed: false,
  },
];

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      viewCompleted: false,
      todoList: todoItems,
    };
  }

  displayCompleted = (status) => {
    if (status) {
      return this.setState({ viewCompleted: true });
    }

    return this.setState({ viewCompleted: false });
  };

  renderTabList = () => {
    return (
      <div className="nav nav-tabs">
        <span
          className={this.state.viewCompleted ? "nav-link active" : "nav-link"}
          onClick={() => this.displayCompleted(true)}
        >
          Complete
        </span>
        <span
          className={this.state.viewCompleted ? "nav-link" : "nav-link active"}
          onClick={() => this.displayCompleted(false)}
        >
          Incomplete
        </span>
      </div>
    );
  };

  renderItems = () => {
    const { viewCompleted } = this.state;
    const newItems = this.state.todoList.filter(
      (item) => item.completed == viewCompleted
    );

    return newItems.map((item) => (
      <li
        key={item.id}
        className="list-group-item d-flex justify-content-between align-items-center"
      >
        <span
          className={`todo-title mr-2 ${
            this.state.viewCompleted ? "completed-todo" : ""
          }`}
          title={item.description}
        >
          {item.title}
        </span>
        <span>
          <button
            className="btn btn-secondary mr-2"
          >
            Edit
          </button>
          <button
            className="btn btn-danger"
          >
            Delete
          </button>
        </span>
      </li>
    ));
  };

  render() {
    return (
      <main className="container">
        <h1 className="text-white text-uppercase text-center my-4">Todo app</h1>
        <div className="row">
          <div className="col-md-6 col-sm-10 mx-auto p-0">
            <div className="card p-3">
              <div className="mb-4">
                <button
                  className="btn btn-primary"
                >
                  Add task
                </button>
              </div>
              {this.renderTabList()}
              <ul className="list-group list-group-flush border-top-0">
                {this.renderItems()}
              </ul>
            </div>
          </div>
        </div>
      </main>
    );
  }
}

export default App;

This code includes some hardcoded values for four items. These will be temporary values until items are fetched from the backend.

The renderTabList() function renders two spans that help control which set of items are displayed. Clicking on the Completed tab will display the completed tasks. Clicking on the Incomplete tab will display the incomplete tasks.

Save your changes and observe the application in your web browser:

Screenshot of the application currently displaying tasks for Study and Article.

To handle actions such as adding and editing tasks, you will need to create a modal component.

First, create a components folder in the src directory:

  1. mkdir src/components

Then, create a Modal.js file and open it with your code editor. Add the following lines of code:

frontend/src/components/Modal.js
import React, { Component } from "react";
import {
  Button,
  Modal,
  ModalHeader,
  ModalBody,
  ModalFooter,
  Form,
  FormGroup,
  Input,
  Label,
} from "reactstrap";

export default class CustomModal extends Component {
  constructor(props) {
    super(props);
    this.state = {
      activeItem: this.props.activeItem,
    };
  }

  handleChange = (e) => {
    let { name, value } = e.target;

    if (e.target.type === "checkbox") {
      value = e.target.checked;
    }

    const activeItem = { ...this.state.activeItem, [name]: value };

    this.setState({ activeItem });
  };

  render() {
    const { toggle, onSave } = this.props;

    return (
      <Modal isOpen={true} toggle={toggle}>
        <ModalHeader toggle={toggle}>Todo Item</ModalHeader>
        <ModalBody>
          <Form>
            <FormGroup>
              <Label for="todo-title">Title</Label>
              <Input
                type="text"
                id="todo-title"
                name="title"
                value={this.state.activeItem.title}
                onChange={this.handleChange}
                placeholder="Enter Todo Title"
              />
            </FormGroup>
            <FormGroup>
              <Label for="todo-description">Description</Label>
              <Input
                type="text"
                id="todo-description"
                name="description"
                value={this.state.activeItem.description}
                onChange={this.handleChange}
                placeholder="Enter Todo description"
              />
            </FormGroup>
            <FormGroup check>
              <Label check>
                <Input
                  type="checkbox"
                  name="completed"
                  checked={this.state.activeItem.completed}
                  onChange={this.handleChange}
                />
                Completed
              </Label>
            </FormGroup>
          </Form>
        </ModalBody>
        <ModalFooter>
          <Button
            color="success"
            onClick={() => onSave(this.state.activeItem)}
          >
            Save
          </Button>
        </ModalFooter>
      </Modal>
    );
  }
}

This code creates a CustomModal class and it nests the Modal component that is derived from the reactstrap library.

This code also defined three fields in the form:

  • title
  • description
  • completed

These are the same fields that we defined as properties on the Todo model in the backend.

The CustomModal receives activeItem, toggle, and onSave as props:

  1. activeItem represents the Todo item to be edited.
  2. toggle is a function used to control the Modal’s state (i.e., open or close the modal).
  3. onSave is a function that is called to save the edited values of the Todo item.

Next, you will import the CustomModal component into the App.js file.

Revisit the src/App.js file with your code editor and replace the entire contents with the following lines of code:

frontend/src/App.js
import React, { Component } from "react";
import Modal from "./components/Modal";

const todoItems = [
  {
    id: 1,
    title: "Go to Market",
    description: "Buy ingredients to prepare dinner",
    completed: true,
  },
  {
    id: 2,
    title: "Study",
    description: "Read Algebra and History textbook for the upcoming test",
    completed: false,
  },
  {
    id: 3,
    title: "Sammy's books",
    description: "Go to library to return Sammy's books",
    completed: true,
  },
  {
    id: 4,
    title: "Article",
    description: "Write article on how to use Django with React",
    completed: false,
  },
];

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      viewCompleted: false,
      todoList: todoItems,
      modal: false,
      activeItem: {
        title: "",
        description: "",
        completed: false,
      },
    };
  }

  toggle = () => {
    this.setState({ modal: !this.state.modal });
  };

  handleSubmit = (item) => {
    this.toggle();

    alert("save" + JSON.stringify(item));
  };

  handleDelete = (item) => {
    alert("delete" + JSON.stringify(item));
  };

  createItem = () => {
    const item = { title: "", description: "", completed: false };

    this.setState({ activeItem: item, modal: !this.state.modal });
  };

  editItem = (item) => {
    this.setState({ activeItem: item, modal: !this.state.modal });
  };

  displayCompleted = (status) => {
    if (status) {
      return this.setState({ viewCompleted: true });
    }

    return this.setState({ viewCompleted: false });
  };

  renderTabList = () => {
    return (
      <div className="nav nav-tabs">
        <span
          className={this.state.viewCompleted ? "nav-link active" : "nav-link"}
          onClick={() => this.displayCompleted(true)}
        >
          Complete
        </span>
        <span
          className={this.state.viewCompleted ? "nav-link" : "nav-link active"}
          onClick={() => this.displayCompleted(false)}
        >
          Incomplete
        </span>
      </div>
    );
  };

  renderItems = () => {
    const { viewCompleted } = this.state;
    const newItems = this.state.todoList.filter(
      (item) => item.completed === viewCompleted
    );

    return newItems.map((item) => (
      <li
        key={item.id}
        className="list-group-item d-flex justify-content-between align-items-center"
      >
        <span
          className={`todo-title mr-2 ${
            this.state.viewCompleted ? "completed-todo" : ""
          }`}
          title={item.description}
        >
          {item.title}
        </span>
        <span>
          <button
            className="btn btn-secondary mr-2"
            onClick={() => this.editItem(item)}
          >
            Edit
          </button>
          <button
            className="btn btn-danger"
            onClick={() => this.handleDelete(item)}
          >
            Delete
          </button>
        </span>
      </li>
    ));
  };

  render() {
    return (
      <main className="container">
        <h1 className="text-white text-uppercase text-center my-4">Todo app</h1>
        <div className="row">
          <div className="col-md-6 col-sm-10 mx-auto p-0">
            <div className="card p-3">
              <div className="mb-4">
                <button
                  className="btn btn-primary"
                  onClick={this.createItem}
                >
                  Add task
                </button>
              </div>
              {this.renderTabList()}
              <ul className="list-group list-group-flush border-top-0">
                {this.renderItems()}
              </ul>
            </div>
          </div>
        </div>
        {this.state.modal ? (
          <Modal
            activeItem={this.state.activeItem}
            toggle={this.toggle}
            onSave={this.handleSubmit}
          />
        ) : null}
      </main>
    );
  }
}

export default App;

Save your changes and observe the application in your web browser:

Screenshot of the Modal component displaying a new task with a title of - Study - and a description of - Read Algebra and History textbook for the upcoming test

If you attempt to edit and save a Todo item, you will get an alert displaying the Todo item’s object. Clicking on Save or Delete will perform the respective actions on the Todo item.

Note:: Depending on your version of React and Reactstrap, you may experience console errors. At the time of the revision, Warning: Legacy context API has been detected within a strict-mode tree. and Warning: findDOMNode is deprecated in StrictMode. are known issues.

Now, you will modify the application so that it interacts with the Django API you built in the previous section. Revisit the first terminal window and ensure the server is running. If it is not running, use the following command:

  1. python manage.py runserver

Note: If you closed this terminal window, remember that you will need to navigate to the backend directory and use the virtual Pipenv shell.

To make requests to the API endpoints on the backend server, you will install a JavaScript library called axios.

In the second terminal window, ensure that you are in the frontend directory and install axios:

  1. npm install axios@0.21.1

Then open the frontend/package.json file in your code editor and add a proxy:

frontend/package.json
[...]
  "name": "frontend",
  "version": "0.1.0",
  "private": true,
  "proxy": "http://localhost:8000",
  "dependencies": {
    "axios": "^0.18.0",
    "bootstrap": "^4.1.3",
    "react": "^16.5.2",
    "react-dom": "^16.5.2",
    "react-scripts": "2.0.5",
    "reactstrap": "^6.5.0"
  },
[...]

The proxy will help in tunneling API requests to http://localhost:8000 where the Django application will handle them. Without this proxy, you would need to specify full paths:

axios.get("http://localhost:8000/api/todos/")

With proxy, you can provide relative paths:

axios.get("/api/todos/")

Note: You might need to restart the development server for the proxy to register with the application.

Revisit the frontend/src/App.js file and open it with your code editor. In this step, you will remove the hardcoded todoItems and use data from requests to the backend server. handleSubmit and handleDelete

Open the App.js file and replace it with this final version:

frontend/src/App.js
import React, { Component } from "react";
import Modal from "./components/Modal";
import axios from "axios";

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      viewCompleted: false,
      todoList: [],
      modal: false,
      activeItem: {
        title: "",
        description: "",
        completed: false,
      },
    };
  }

  componentDidMount() {
    this.refreshList();
  }

  refreshList = () => {
    axios
      .get("/api/todos/")
      .then((res) => this.setState({ todoList: res.data }))
      .catch((err) => console.log(err));
  };

  toggle = () => {
    this.setState({ modal: !this.state.modal });
  };

  handleSubmit = (item) => {
    this.toggle();

    if (item.id) {
      axios
        .put(`/api/todos/${item.id}/`, item)
        .then((res) => this.refreshList());
      return;
    }
    axios
      .post("/api/todos/", item)
      .then((res) => this.refreshList());
  };

  handleDelete = (item) => {
    axios
      .delete(`/api/todos/${item.id}/`)
      .then((res) => this.refreshList());
  };

  createItem = () => {
    const item = { title: "", description: "", completed: false };

    this.setState({ activeItem: item, modal: !this.state.modal });
  };

  editItem = (item) => {
    this.setState({ activeItem: item, modal: !this.state.modal });
  };

  displayCompleted = (status) => {
    if (status) {
      return this.setState({ viewCompleted: true });
    }

    return this.setState({ viewCompleted: false });
  };

  renderTabList = () => {
    return (
      <div className="nav nav-tabs">
        <span
          onClick={() => this.displayCompleted(true)}
          className={this.state.viewCompleted ? "nav-link active" : "nav-link"}
        >
          Complete
        </span>
        <span
          onClick={() => this.displayCompleted(false)}
          className={this.state.viewCompleted ? "nav-link" : "nav-link active"}
        >
          Incomplete
        </span>
      </div>
    );
  };

  renderItems = () => {
    const { viewCompleted } = this.state;
    const newItems = this.state.todoList.filter(
      (item) => item.completed === viewCompleted
    );

    return newItems.map((item) => (
      <li
        key={item.id}
        className="list-group-item d-flex justify-content-between align-items-center"
      >
        <span
          className={`todo-title mr-2 ${
            this.state.viewCompleted ? "completed-todo" : ""
          }`}
          title={item.description}
        >
          {item.title}
        </span>
        <span>
          <button
            className="btn btn-secondary mr-2"
            onClick={() => this.editItem(item)}
          >
            Edit
          </button>
          <button
            className="btn btn-danger"
            onClick={() => this.handleDelete(item)}
          >
            Delete
          </button>
        </span>
      </li>
    ));
  };

  render() {
    return (
      <main className="container">
        <h1 className="text-white text-uppercase text-center my-4">Todo app</h1>
        <div className="row">
          <div className="col-md-6 col-sm-10 mx-auto p-0">
            <div className="card p-3">
              <div className="mb-4">
                <button
                  className="btn btn-primary"
                  onClick={this.createItem}
                >
                  Add task
                </button>
              </div>
              {this.renderTabList()}
              <ul className="list-group list-group-flush border-top-0">
                {this.renderItems()}
              </ul>
            </div>
          </div>
        </div>
        {this.state.modal ? (
          <Modal
            activeItem={this.state.activeItem}
            toggle={this.toggle}
            onSave={this.handleSubmit}
          />
        ) : null}
      </main>
    );
  }
}

export default App;

The refreshList() function is reusable that is called each time an API request is completed. It updates the Todo list to display the most recent list of added items.

The handleSubmit() function takes care of both the create and update operations. If the item passed as the parameter doesn’t have an id, then it has probably not been created, so the function creates it.

At this point, verify that your backend server is running in your first terminal window:

  1. python manage.py runserver

Note: If you closed this terminal window, remember that you will need to navigate to the backend directory and use the virtual Pipenv shell.

And in your second terminal window, ensure that you are in the frontend directory and start your frontend application:

  1. npm start

Now when you visit http://localhost:3000 with your web browser, your application will allow you to READ, CREATE, UPDATE, and DELETE tasks.

Animated gif of a user interacting with the application to create a new item and toggle it between complete and incomplete statuses.

This completes the frontend and backend of the Todo application.

Conclusion

In this article, you built a To-Do application using Django and React. You achieved this with the djangorestframework, django-cors-headers, axios, bootstrap, and reactstrap libraries.

If you’d like to learn more about Django, check out our Django topic page for exercises and programming projects.

If you’d like to learn more about React, take a look at our How To Code in React.js series, or check out our React topic page for exercises and programming projects.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the authors
Default avatar
Jordan Irabor

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
10 Comments


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Thanks for the great tutorial. For those still struggling with the POST,PUT,DELETE or related requests, it’s most likely because your requests aren’t authenticated. A quick fix would be to first create a crsftoken then include it in the requests. Here’s a related code snippet for the fix.

How to deploy this to DigitalOcean?

What are the pros and cons of using Django Templates for the front-end in place of React?

Thanks for the tutorial. Everything works great, though for future readers I would point the 2 problems I most had trouble with

  1. “?: (corsheaders.E013) Origin ‘localhost:3000/’ in CORSORIGINWHITELIST As others have mentioned I encountered this error early on the solution for me was:
CORS_ORIGIN_WHITELIST = [
     'https://localhost:3000'
]
  1. The proxy for the axios in the frontend is supposed to change
axios.get("http://localhost:8000/api/todos/")

to

axios.get("/api/todos/")

However, the final App.js file is still using the former one which caused my list to not show up. You need to edit and change them to the latter in App.js

  1. You added localhost:8000 as a proxy and you mentioned the purpose of it. However, you still left it in your axios requests

  2. I believe you’ll be getting forbidden requests unless you include CSRF tokens as well.

  3. you already used npm to create the react app. Why are you suddenly using yarn? The tutorial gets very confusing when it’s just blindly copy/pasting code.

great article, thanks a lot !! If you ran into the error “?: (corsheaders.E013) Origin ‘localhost:3000/’ in CORSORIGINWHITELIST is missing scheme or netloc HINT: Add a scheme (e.g. https://) or netloc (e.g. example.com).” Same as bellow try this

CORS_ORIGIN_WHITELIST = [
     'https://localhost:3000'
]

Today I learn that django care about the brackets !

This is my error request failed with status code 404 someone help me greetings image in the link thank you

https://drive.google.com/file/d/1TmNQgLI8XprNeBYwsIynKfOd-udUQir2/view?usp=share_link

Excellent tutorial!

In case you’re having issues with submitted todos actually showing up in the app, the proxy might need to be reconfigured

proxy: "http://localhost:8000/

in my case it the correct proxy actually “http://127.0.0.1:8000/

To find this look up the printout from the Terminal running the backend:

System check identified no issues (0 silenced). Django version 4.1.5, using settings ‘backend.settings’ Starting development server at http://127.0.0.1:8000/

Hope this helps!

Pretty good project. Thanks for sharing. A couple of things that needed more explanation but was fun to find the solutions. Thanks again!

can someone help me with the deployment of this app

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Become a contributor for community

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

DigitalOcean Documentation

Full documentation for every DigitalOcean product.

Resources for startups and SMBs

The Wave has everything you need to know about building a business, from raising funding to marketing your product.

Get our newsletter

Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.

New accounts only. By submitting your email you agree to our Privacy Policy

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Get started for free

Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

*This promotional offer applies to new accounts only.