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_projectCreate 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.titleRun migrations:
python manage.py makemigrations
python manage.py migrate3. 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 = TodoSerializer5. 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 runserverVisit 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 runserverTest 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-frontendInstall Axios
Axios is a library for making HTTP requests, which we'll use to interact with the API.
npm install axios2. Create the To-Do Components
Directory Structure
Organize your files as follows:
src/
├── components/
│ ├── TodoList.js
│ ├── TodoForm.js
├── App.js
├── index.jsTodoList 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 start4. 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-headersAdd 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 = TrueWith this setup, your React frontend should now be fully integrated with your Django REST API!
Thank you for your reading.
📤 Share this article
Sign in to saveadmin
Writer at Bitsfolio. Passionate about Python, Data Analytics, and making complex tech topics accessible.
View all articles →Related Articles
Comments (0)
No comments yet. Be the first!