Build a Full-Stack CRUD App: A Beginner’s Guide to Django & React JS

By admin December 22, 2025 13 min read

Welcome, developers! Today, we’re embarking on an exciting journey to build a full-stack web application. We’ll combine the robust, Python-powered backend of Django with the dynamic, component-based frontend of React JS. By the end of this tutorial, you’ll have a fully functional Task Manager application that can Create, Read, Update, and Delete (CRUD) tasks.

This guide is designed for beginner to intermediate developers. We’ll move step-by-step, explaining concepts clearly. We’ll be using the code from a companion GitHub repository as our blueprint. Let’s dive in!

To watch the full tutorial on YouTube, click here.

Prerequisites and Project Overview

Before we start coding, ensure you have the following installed on your system:

  • Python (3.8+) and pip
  • Node.js (14+) and npm
  • Git (optional, but helpful)

Our project will be structured as two separate but connected parts:

  1. Backend (Django): A RESTful API built with Django and Django REST Framework (DRF). It will handle data logic, database operations, and expose endpoints like /api/tasks/.
  2. Frontend (React): A single-page application that consumes the backend API. It will provide the user interface to view, add, edit, and delete tasks.

Tip: Think of the backend as the kitchen (preparing and managing data) and the frontend as the dining area (presenting data and interacting with the user). They communicate through a specific window: the API.

Part 1: Building the Django REST API Backend

Let’s start by setting up our Django project and creating the API that will serve our task data.

1.1 Setting Up the Django Project and Virtual Environment

First, create a project directory and a Python virtual environment to manage dependencies.


# Create and navigate to your project directory
mkdir crud-django-react
cd crud-django-react

# Create a virtual environment (named 'venv')
python -m venv venv

# Activate the virtual environment
# On Windows:
venv\Scripts\activate
# On macOS/Linux:
source venv/bin/activate

# Your terminal prompt should now show (venv)

1.2 Installing Dependencies and Creating the Project

With the virtual environment active, install Django and Django REST Framework. Then, create the Django project and a new app within it.


# Install required Python packages
pip install django djangorestframework django-cors-headers

# Create the Django project named 'backend'
django-admin startproject backend .

# Create a Django app named 'tasks'
python manage.py startapp tasks

Now, let’s register our new app and necessary third-party apps in backend/settings.py.


# backend/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # Third-party apps
    'rest_framework',
    'corsheaders',
    # Local app
    'tasks',
]

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

# Configure CORS to allow requests from our React frontend (running on port 3000)
CORS_ALLOWED_ORIGINS = [
    "http://localhost:3000",
]

# At the end of the file, configure static files (optional for now)
STATIC_URL = 'static/'

1.3 Defining the Task Model

The model is the heart of our data structure. It defines what a “Task” looks like in our database. Open tasks/models.py.


# tasks/models.py
from django.db import models

class Task(models.Model):
    """
    Model representing a single task.
    """
    title = models.CharField(max_length=200)
    description = models.TextField(blank=True, null=True)
    completed = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        """String representation of the model."""
        return self.title

After defining the model, we need to create the database tables.


# Create migrations for the new Task model
python manage.py makemigrations tasks

# Apply the migrations to the database
python manage.py migrate

1.4 Creating the Serializer

Serializers allow us to convert complex data types (like Django model instances) into native Python data types (like dictionaries) that can then be easily rendered into JSON for our API. Create a new file: tasks/serializers.py.


# tasks/serializers.py
from rest_framework import serializers
from .models import Task

class TaskSerializer(serializers.ModelSerializer):
    """
    Serializer for the Task model.
    Converts Task instances to/from JSON format.
    """
    class Meta:
        model = Task
        fields = '__all__'  # Includes all model fields: id, title, description, completed, created_at

1.5 Building API Views with ViewSets

Views handle the logic for our API endpoints. Django REST Framework’s ModelViewSet provides default actions for CRUD operations. Let’s edit tasks/views.py.


# tasks/views.py
from rest_framework import viewsets
from .models import Task
from .serializers import TaskSerializer

class TaskViewSet(viewsets.ModelViewSet):
    """
    A ViewSet for viewing and editing Task instances.
    Provides: list, create, retrieve, update, partial_update, destroy.
    """
    queryset = Task.objects.all().order_by('-created_at')  # Order by newest first
    serializer_class = TaskSerializer

1.6 Wiring Up the URLs

We need to connect our views to specific URL patterns. First, create a urls.py file inside the tasks app.


# tasks/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import TaskViewSet

# Create a router and register our ViewSet
router = DefaultRouter()
router.register(r'tasks', TaskViewSet)

# The API URLs are now determined automatically by the router
urlpatterns = [
    path('', include(router.urls)),
]

Now, include the tasks app URLs in the main project’s backend/urls.py.


# backend/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('tasks.urls')),  # All our API routes start with /api/
]

1.7 Testing the Backend API

Let’s run the Django development server and test our API endpoint.


# Run the Django server on port 8000
python manage.py runserver

Open your browser and go to http://localhost:8000/api/tasks/. You should see the DRF browsable API interface for tasks, which is currently empty. You can use this interface to create a few test tasks manually.

Key Takeaway: At this point, we have a fully functional REST API. It can handle GET (list tasks), POST (create task), PUT/PATCH (update task), and DELETE operations, all accessible via the /api/tasks/ endpoint. The frontend will now consume this.

Part 2: Building the React JS Frontend

With our API ready, let’s shift focus to the user interface. We’ll create a React application that communicates with our Django backend.

2.1 Setting Up the React Project

Open a new terminal window (keep your Django server running in the first one). Navigate to your project’s root directory (crud-django-react) and create the React app.


# Make sure you are in the 'crud-django-react' directory, not inside 'backend'
npx create-react-app frontend
cd frontend

We’ll install additional libraries for making HTTP requests and styling.


# Install Axios for API calls and React Bootstrap for UI components
npm install axios react-bootstrap bootstrap

2.2 Project Structure and Global Styles

Inside the src folder, we’ll organize our components. Let’s first add Bootstrap CSS to src/index.js.


// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import 'bootstrap/dist/css/bootstrap.min.css'; // Import Bootstrap CSS

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

2.3 Creating the API Service (Axios Configuration)

It’s a good practice to centralize API configuration. Create a file src/services/api.js.


// src/services/api.js
import axios from 'axios';

// Create an Axios instance with a base URL pointing to our Django backend
const api = axios.create({
  baseURL: 'http://localhost:8000/api/', // Your Django server address
  headers: {
    'Content-Type': 'application/json',
  },
});

export default api;

2.4 The Main App Component and Routing

We’ll use React Router for navigation. Install it first.

npm install react-router-dom

Now, let’s set up the main App.js component with a basic layout and routing.


// src/App.js
import React from 'react';
import { Container } from 'react-bootstrap';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import NavigationBar from './components/NavigationBar';
import TaskList from './components/TaskList';
import TaskForm from './components/TaskForm';

function App() {
  return (
    <Router>
      <NavigationBar />
      <Container className="mt-4">
        <Routes>
          <Route path="/" element={<TaskList />} />
          <Route path="/create" element={<TaskForm />} />
          <Route path="/edit/:id" element={<TaskForm />} />
        </Routes>
      </Container>
    </Router>
  );
}

export default App;

2.5 Building the Navigation Bar Component

Create src/components/NavigationBar.js.


// src/components/NavigationBar.js
import React from 'react';
import { Navbar, Nav, Container } from 'react-bootstrap';
import { LinkContainer } from 'react-router-bootstrap';

const NavigationBar = () => {
  return (
    <Navbar bg="dark" variant="dark" expand="lg">
      <Container>
        <LinkContainer to="/">
          <Navbar.Brand>Task Manager</Navbar.Brand>
        </LinkContainer>
        <Navbar.Toggle aria-controls="basic-navbar-nav" />
        <Navbar.Collapse id="basic-navbar-nav">
          <Nav className="ms-auto">
            <LinkContainer to="/">
              <Nav.Link>Home</Nav.Link>
            </LinkContainer>
            <LinkContainer to="/create">
              <Nav.Link>Create New Task</Nav.Link>
            </LinkContainer>
          </Nav>
        </Navbar.Collapse>
      </Container>
    </Navbar>
  );
};

export default NavigationBar;

2.6 The TaskList Component (Read Operation)

This component fetches and displays all tasks. It also handles task deletion. Create src/components/TaskList.js.


// src/components/TaskList.js
import React, { useState, useEffect } from 'react';
import { Table, Button, Badge, Spinner, Alert } from 'react-bootstrap';
import { Link } from 'react-router-dom';
import api from '../services/api';

const TaskList = () => {
  const [tasks, setTasks] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  // Fetch tasks from API on component mount
  const fetchTasks = async () => {
    try {
      setLoading(true);
      const response = await api.get('/tasks/');
      setTasks(response.data);
      setError(null);
    } catch (err) {
      console.error('Error fetching tasks:', err);
      setError('Failed to load tasks. Please check if the backend server is running.');
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchTasks();
  }, []); // Empty dependency array means run once on mount

  // Function to delete a task
  const handleDelete = async (id) => {
    if (window.confirm('Are you sure you want to delete this task?')) {
      try {
        await api.delete(`/tasks/${id}/`);
        // Remove the task from the local state to update the UI immediately
        setTasks(tasks.filter(task => task.id !== id));
      } catch (err) {
        console.error('Error deleting task:', err);
        alert('Failed to delete task.');
      }
    }
  };

  if (loading) {
    return (
      <div className="text-center mt-5">
        <Spinner animation="border" variant="primary" />
        <p className="mt-2">Loading tasks...</p>
      </div>
    );
  }

  return (
    <>
      <h2 className="mb-4">Task List</h2>
      {error && <Alert variant="danger">{error}</Alert>}
      {tasks.length === 0 && !error ? (
        <Alert variant="info">No tasks found. Create your first task!</Alert>
      ) : (
        <Table striped bordered hover responsive>
          <thead>
            <tr>
              <th>Title</th>
              <th>Description</th>
              <th>Status</th>
              <th>Actions</th>
            </tr>
          </thead>
          <tbody>
            {tasks.map((task) => (
              <tr key={task.id}>
                <td><strong>{task.title}</strong></td>
                <td>{task.description || 'N/A'}</td>
                <td>
                  <Badge bg={task.completed ? "success" : "warning"}>
                    {task.completed ? "Completed" : "Pending"}
                  </Badge>
                </td>
                <td>
                  <Link to={`/edit/${task.id}`}>
                    <Button variant="outline-primary" size="sm" className="me-2">Edit</Button>
                  </Link>
                  <Button variant="outline-danger" size="sm" onClick={() => handleDelete(task.id)}>
                    Delete
                  </Button>
                </td>
              </tr>
            ))}
          </tbody>
        </Table>
      )}
    </>
  );
};

export default TaskList;

2.7 The TaskForm Component (Create & Update Operations)

This reusable component handles both creating a new task and editing an existing one. Create src/components/TaskForm.js.


// src/components/TaskForm.js
import React, { useState, useEffect } from 'react';
import { Form, Button, Card, Spinner, Alert } from 'react-bootstrap';
import { useParams, useNavigate, Link } from 'react-router-dom';
import api from '../services/api';

const TaskForm = () => {
  const { id } = useParams(); // Get task ID from URL if editing
  const navigate = useNavigate();
  const isEditMode = !!id; // Check if we are in edit mode

  const [formData, setFormData] = useState({
    title: '',
    description: '',
    completed: false,
  });
  const [loading, setLoading] = useState(false);
  const [fetching, setFetching] = useState(isEditMode); // Only fetch if editing
  const [error, setError] = useState(null);

  // Fetch task data if in edit mode
  useEffect(() => {
    const fetchTask = async () => {
      if (!isEditMode) return;
      try {
        const response = await api.get(`/tasks/${id}/`);
        setFormData(response.data);
        setError(null);
      } catch (err) {
        console.error('Error fetching task:', err);
        setError('Failed to load task data.');
      } finally {
        setFetching(false);
      }
    };
    fetchTask();
  }, [id, isEditMode]);

  // Handle input changes
  const handleChange = (e) => {
    const { name, value, type, checked } = e.target;
    setFormData({
      ...formData,
      [name]: type === 'checkbox' ? checked : value,
    });
  };

  // Handle form submission
  const handleSubmit = async (e) => {
    e.preventDefault();
    setLoading(true);
    setError(null);

    try {
      if (isEditMode) {
        // Update existing task
        await api.put(`/tasks/${id}/`, formData);
      } else {
        // Create new task
        await api.post('/tasks/', formData);
      }
      navigate('/'); // Redirect to the task list after success
    } catch (err) {
      console.error('Error saving task:', err);
      setError(`Failed to ${isEditMode ? 'update' : 'create'} task. Please check your input.`);
    } finally {
      setLoading(false);
    }
  };

  if (fetching) {
    return (
      <div className="text-center mt-5">
        <Spinner animation="border" variant="info" />
        <p className="mt-2">Loading task data...</p>
      </div>
    );
  }

  return (
    <Card className="p-4 shadow">
      <Card.Body>
        <h2 className="mb-4">{isEditMode ? 'Edit Task' : 'Create New Task'}</h2>
        {error && <Alert variant="danger">{error}</Alert>}
        <Form onSubmit={handleSubmit}>
          <Form.Group className="mb-3" controlId="formTitle">
            <Form.Label>Task Title *</Form.Label>
            <Form.Control
              type="text"
              placeholder="Enter task title"
              name="title"
              value={formData.title}
              onChange={handleChange}
              required
            />
          </Form.Group>

          <Form.Group className="mb-3" controlId="formDescription">
            <Form.Label>Description</Form.Label>
            <Form.Control
              as="textarea"
              rows={3}
              placeholder="Enter task description (optional)"
              name="description"
              value={formData.description}
              onChange={handleChange}
            />
          </Form.Group>

          <Form.Group className="mb-3" controlId="formCompleted">
            <Form.Check
              type="checkbox"
              label="Task Completed?"
              name="completed"
              checked={formData.completed}
              onChange={handleChange}
            />
          </Form.Group>

          <div className="d-flex justify-content-between">
            <Link to="/">
              <Button variant="outline-secondary">Cancel</Button>
            </Link>
            <Button variant="primary" type="submit" disabled={loading}>
              {loading ? (
                <><Spinner animation="border" size="sm" /> Saving...</>
              ) : (
                <>{isEditMode ? 'Update Task' : 'Create Task'}</>
              )}
            </Button>
          </div>
        </Form>
      </Card.Body>
    </Card>
  );
};

export default TaskForm;

Part 3: Running the Full-Stack Application

Now comes the moment of truth! We need to run both the backend and frontend servers concurrently.

  1. Terminal 1 (Backend): Ensure your Django server is still running on http://localhost:8000. If not, activate your virtual environment and run:
  2. python manage.py runserver
  3. Terminal 2 (Frontend): Navigate to the frontend directory and start the React development server.
  4. cd frontend
    npm start

Your browser should automatically open to http://localhost:3000. You should now see your fully functional Task Manager!

  • Click “Create New Task” to add a task.
  • On the home page, click “Edit” to update a task’s details or mark it as completed.
  • Click “Delete” to remove a task.

Congratulations! You’ve just built a complete full-stack CRUD application. The React frontend sends HTTP requests (GET, POST, PUT, DELETE) to the Django REST API, which processes them and interacts with the database. This is the fundamental architecture of modern web applications.

Next Steps and Further Learning

You’ve built a solid foundation. Here are some ideas to enhance this project and deepen your skills:

  • Add User Authentication: Use Django’s django-rest-auth or dj-rest-auth to allow users to sign up and manage their own private tasks.
  • Improve UI/UX: Add more feedback with toasts (try react-toastify), implement form validation messages, or add a search/filter feature to the task list.
  • Deploy It! Learn about deploying Django on platforms like Heroku or PythonAnywhere, and React on Vercel or Netlify. Remember to configure CORS and environment variables for production.
  • Explore State Management: For larger applications, consider using a state management library like Redux or React Context API to manage global application state more effectively.

We hope this tutorial empowered you to understand the synergy between Django and React. The full source code is available on GitHub. Feel free to clone it, experiment, and make it your own.

To watch the full tutorial on YouTube, click here.

Happy coding! For more web development tutorials and tech insights, explore other articles on Halogenius Ideas.

Watch The Video

Watch on YouTube

How did this article make you feel?

Share This Post

About admin

Founder and Lead Developer at Halogenius Ideas, bridging the gap between professional web design and accessible tech education. With years of experience in full-stack development and a passion for teaching, I lead a team dedicated to building stunning digital experiences while empowering the next generation of developers through comprehensive tutorials and courses. When I'm not coding or creating content, you'll find me exploring new technologies and mentoring aspiring developers.

Leave a Reply

Your email address will not be published. Required fields are marked *

fifty one + = 56
Powered by MathCaptcha