Published on Nov 28, 2024 Updated on Dec 22, 2024

Build a To-Do App with Django REST Framework (DRF) Backend and React.js Frontend

Creating a modern web application involves combining a robust backend with an intuitive and dynamic frontend. In this guide, we’ll walk you through building a fully functional To-Do app that seamlessly integrates Django REST Framework (DRF) for the backend and React.js for the frontend. DRF provides a powerful, flexible API layer, while React enhances the user experience with interactive and responsive design. By the end of this project, you’ll have a solid understanding of how to connect these two technologies to create a practical, real-world application that is both efficient and visually appealing. Whether you're new to full-stack development or looking to refine your skills, this step-by-step approach will set you up for success.

 

 

Creating a Django REST API for a To-Do List project is a great way to practice working with Django and the Django REST Framework (DRF). Below is a step-by-step guide to building the project:

 

Backend Develop

1. Setup the Environment 

Install Django and Django REST Framework

pip install django djangorestframework

Start a Django Project

django-admin startproject todo_project
cd todo_project

Create a New App

python manage.py startapp todo

Add the app and DRF to INSTALLED_APPS in todo_project/settings.py:

INSTALLED_APPS = [
    ...
    'rest_framework',
    'todo',
]

2. Define the Model

In todo/models.py, define a model for your To-Do items:

from django.db import models

class Todo(models.Model):
    title = models.CharField(max_length=255)
    description = models.TextField(blank=True, null=True)
    is_completed = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.title

Run migrations:

python manage.py makemigrations
python manage.py migrate

3. Create a Serializer

In todo/serializers.py:

from rest_framework import serializers
from .models import Todo

class TodoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Todo
        fields = '__all__'

4. Build the Views

In todo/views.py:

from rest_framework import viewsets
from .models import Todo
from .serializers import TodoSerializer

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

5. Configure the URLs

In todo/urls.py:

from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import TodoViewSet

router = DefaultRouter()
router.register(r'todos', TodoViewSet)

urlpatterns = [
    path('', include(router.urls)),
]

Include the app's URLs in the project urls.py:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('todo.urls')),  # API endpoints
]

6. Test the API

Run the server:

python manage.py runserver

Visit http://127.0.0.1:8000/api/todos/ in your browser or use a tool like Postman to interact with the API.

Endpoints

  • GET /api/todos/ - List all To-Do items
  • POST /api/todos/ - Create a new To-Do item
  • GET /api/todos/{id}/ - Retrieve a specific To-Do item
  • PUT /api/todos/{id}/ - Update a specific To-Do item
  • DELETE /api/todos/{id}/ - Delete a specific To-Do item

7. Enhancements (Optional)
 

  • Authentication: Use DRF's authentication to secure the API.
  • Pagination: Add pagination to limit the number of items in the response.
  • Filtering/Sorting: Use DRF's filtering features to search or sort items.
  • Frontend Integration: Connect this API to a frontend framework like React or Vue.js for a complete app.

 

If you prefer function-based views (FBVs) instead of class-based views, here's how you can build the To-Do List API.

 

1. Update Views
Replace the viewsets approach with function-based views using Django REST Framework's @api_view decorator.

In todo/views.py:

from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
from .models import Todo
from .serializers import TodoSerializer

@api_view(['GET', 'POST'])
def todo_list(request):
    """
    Handle GET for listing all to-dos and POST for creating a new to-do.
    """
    if request.method == 'GET':
        todos = Todo.objects.all()
        serializer = TodoSerializer(todos, many=True)
        return Response(serializer.data)

    elif request.method == 'POST':
        serializer = TodoSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


@api_view(['GET', 'PUT', 'DELETE'])
def todo_detail(request, pk):
    """
    Handle GET, PUT, and DELETE for a specific to-do item.
    """
    try:
        todo = Todo.objects.get(pk=pk)
    except Todo.DoesNotExist:
        return Response({'error': 'Todo not found'}, status=status.HTTP_404_NOT_FOUND)

    if request.method == 'GET':
        serializer = TodoSerializer(todo)
        return Response(serializer.data)

    elif request.method == 'PUT':
        serializer = TodoSerializer(todo, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    elif request.method == 'DELETE':
        todo.delete()
        return Response({'message': 'Todo deleted successfully'}, status=status.HTTP_204_NO_CONTENT)

2. Update URLs
In todo/urls.py:

from django.urls import path
from . import views

urlpatterns = [
    path('todos/', views.todo_list, name='todo-list'),
    path('todos/<int:pk>/', views.todo_detail, name='todo-detail'),
]

Include these URLs in your main urls.py:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('todo.urls')),  # Include To-Do app API URLs
]

3. Testing the API

Run the server:

python manage.py runserver

Test the endpoints using a tool like Postman, cURL, or a browser for GET requests.

Endpoints

  • GET /api/todos/ - List all To-Do items
  • POST /api/todos/ - Create a new To-Do item
  • GET /api/todos/{id}/ - Retrieve a specific To-Do item
  • PUT /api/todos/{id}/ - Update a specific To-Do item
  • DELETE /api/todos/{id}/ - Delete a specific To-Do item

4. Example API Calls
Create a To-Do (POST /api/todos/)
Request Body (JSON):

{
    "title": "Learn Django",
    "description": "Build a To-Do app using Django REST Framework",
    "is_completed": false
}

Update a To-Do (PUT /api/todos/1/)
Request Body (JSON):

{
    "title": "Learn Django REST",
    "description": "Build an app with FBVs",
    "is_completed": true
}

This approach provides a simple and lightweight way to implement a REST API using function-based views. 

 

Frontend Develop

Integrating your Django REST API with a React.js frontend is a great idea! Below is a step-by-step guide to building a React frontend for your To-Do list API.

1. Setup the React Project
Install React

npx create-react-app todo-frontend
cd todo-frontend

Install Axios
Axios is a library for making HTTP requests, which we'll use to interact with the API.

npm install axios

2. Create the To-Do Components
 Directory Structure
Organize your files as follows:

src/
├── components/
│   ├── TodoList.js
│   ├── TodoForm.js
├── App.js
├── index.js

TodoList Component
 The TodoList component will fetch and display the list of To-Do items from the API.

import React, { useState, useEffect } from 'react';
import axios from 'axios';

const TodoList = ({ onEdit }) => {
  const [todos, setTodos] = useState([]);

  // Fetch todos on component mount
  useEffect(() => {
    axios
      .get('http://127.0.0.1:8000/api/todos/')
      .then((response) => setTodos(response.data))
      .catch((error) => console.error('Error fetching todos:', error));
  }, []);

  const handleDelete = (id) => {
    axios
      .delete(`http://127.0.0.1:8000/api/todos/${id}/`)
      .then(() => {
        setTodos(todos.filter((todo) => todo.id !== id));
      })
      .catch((error) => console.error('Error deleting todo:', error));
  };

  return (
    <div>
      <h2>To-Do List</h2>
      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>
            <strong>{todo.title}</strong> - {todo.description}
            <button onClick={() => onEdit(todo)}>Edit</button>
            <button onClick={() => handleDelete(todo.id)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default TodoList;

TodoForm Component
 The TodoForm component will handle adding and editing To-Do items.

import React, { useState } from 'react';
import axios from 'axios';

const TodoForm = ({ currentTodo, clearEdit }) => {
  const [title, setTitle] = useState(currentTodo?.title || '');
  const [description, setDescription] = useState(currentTodo?.description || '');
  const [isCompleted, setIsCompleted] = useState(currentTodo?.is_completed || false);

  const handleSubmit = (event) => {
    event.preventDefault();

    const todoData = { title, description, is_completed: isCompleted };

    if (currentTodo) {
      // Update an existing To-Do
      axios
        .put(`http://127.0.0.1:8000/api/todos/${currentTodo.id}/`, todoData)
        .then(() => {
          clearEdit();
        })
        .catch((error) => console.error('Error updating todo:', error));
    } else {
      // Create a new To-Do
      axios
        .post('http://127.0.0.1:8000/api/todos/', todoData)
        .then(() => {
          setTitle('');
          setDescription('');
          setIsCompleted(false);
        })
        .catch((error) => console.error('Error creating todo:', error));
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <h2>{currentTodo ? 'Edit To-Do' : 'Add New To-Do'}</h2>
      <input
        type="text"
        placeholder="Title"
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        required
      />
      <textarea
        placeholder="Description"
        value={description}
        onChange={(e) => setDescription(e.target.value)}
      />
      <label>
        <input
          type="checkbox"
          checked={isCompleted}
          onChange={(e) => setIsCompleted(e.target.checked)}
        />
        Completed
      </label>
      <button type="submit">{currentTodo ? 'Update' : 'Add'}</button>
      {currentTodo && <button onClick={clearEdit}>Cancel</button>}
    </form>
  );
};

export default TodoForm;

App Component
 Combine the TodoList and TodoForm components in the main App.js.

import React, { useState } from 'react';
import TodoList from './components/TodoList';
import TodoForm from './components/TodoForm';

const App = () => {
  const [currentTodo, setCurrentTodo] = useState(null);

  const handleEdit = (todo) => {
    setCurrentTodo(todo);
  };

  const clearEdit = () => {
    setCurrentTodo(null);
  };

  return (
    <div>
      <h1>To-Do App</h1>
      <TodoForm currentTodo={currentTodo} clearEdit={clearEdit} />
      <TodoList onEdit={handleEdit} />
    </div>
  );
};

export default App;

3. Run the React App
 Start the React development server:

npm start

4. Connecting Django and React
 Enable Cross-Origin Requests
Install and configure Django CORS Headers to allow the React app to access the API.

pip install django-cors-headers

Add corsheaders to INSTALLED_APPS in settings.py:

INSTALLED_APPS = [
    ...
    'corsheaders',
]

Add CorsMiddleware to MIDDLEWARE:

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    ...
]

Allow all origins (for development purposes) by adding this to settings.py:

CORS_ALLOW_ALL_ORIGINS = True

With this setup, your React frontend should now be fully integrated with your Django REST API!

Thank you for your reading.