Build a CRUD Table with Django, React & Material UI: A Full-Stack Tutorial
Build a Dynamic CRUD Table with Django, React, and Material UI
Welcome back to Halogenius Ideas! Today, we’re embarking on an exciting journey to build a full-stack web application. We’ll create a sleek, functional CRUD (Create, Read, Update, Delete) interface for managing a list of items. By the end of this tutorial, you’ll have a working application with a Django backend serving a REST API and a React frontend styled with the beautiful and popular Material UI component library.
This project is perfect for beginner to intermediate developers looking to understand how separate frontend and backend systems communicate. We’ll walk through every file, explain each concept, and you’ll end up with a project structure you can expand for your own ideas. Let’s get our tools ready and start building!
To watch the full tutorial on YouTube, click here.
Key Takeaway: A CRUD application represents the four basic operations of persistent storage. Mastering this pattern is foundational to nearly all interactive web applications you will build.
Prerequisites and Project Setup
Before we dive into the code, let’s ensure we have the right environment. You’ll need Python (3.8 or higher) and Node.js (with npm) installed on your machine. We’ll be using a virtual environment for Python to manage dependencies cleanly.
First, let’s create our project directory and set up the backend (Django) and frontend (React) as two separate services. This is a modern, decoupled architecture.
# Create the main project folder
mkdir crud-table-project
cd crud-table-project
# Create and activate a Python virtual environment (Backend)
python -m venv venv
# On Windows: venv\Scripts\activate
# On Mac/Linux: source venv/bin/activate
Part 1: Building the Django Backend API
Our backend will be a Django project using the Django REST Framework (DRF) to create a robust and simple API. Think of Django as the reliable librarian who manages all our data (books) in an organized database, and DRF as the helpful assistant who neatly packages that data to send to our frontend.
Step 1: Install Dependencies and Create Project
With your virtual environment activated, install the necessary Python packages and start the Django project.
pip install django djangorestframework django-cors-headers
django-admin startproject backend
cd backend
Step 2: Create the “Item” App and Model
In Django, functionality is organized into “apps.” We’ll create an app called items to handle our data model and API logic. Our model will be simple: an Item with a name and a description.
python manage.py startapp items
Now, let’s define our model in items/models.py. This code defines the structure of our database table.
from django.db import models
class Item(models.Model):
"""
Model representing an Item in the system.
Fields:
name - A short identifier for the item (CharField).
description - A longer text describing the item (TextField).
"""
name = models.CharField(max_length=200)
description = models.TextField()
def __str__(self):
"""String representation of the Item model."""
return self.name
Step 3: Configure the Project Settings
We need to register our new app and necessary third-party apps in backend/settings.py. Crucially, we must also configure CORS (Cross-Origin Resource Sharing) to allow our React frontend (running on a different port) to communicate with the Django backend.
# backend/settings.py (Excerpts)
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 apps
'items',
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware', # <-- Add this at the top
'django.middleware.security.SecurityMiddleware',
... # other middleware
]
# CORS Configuration
CORS_ALLOWED_ORIGINS = [
"http://localhost:3000", # This is the default React development server port
]
# For simplicity in development, you can allow all origins (NOT for production)
# CORS_ALLOW_ALL_ORIGINS = True
Step 4: Create Serializers and Views
A serializer translates complex data (like model instances) into native Python datatypes (like dictionaries) that can be easily rendered into JSON. Our views will handle the logic for each API endpoint (GET, POST, PUT, DELETE).
First, create items/serializers.py:
from rest_framework import serializers
from .models import Item
class ItemSerializer(serializers.ModelSerializer):
"""
Serializer for the Item model.
Translates Item instances to/from JSON format.
"""
class Meta:
model = Item
fields = ['id', 'name', 'description'] # Fields to include in the API
Next, update items/views.py to define the API logic. We'll use DRF's generic class-based views which provide common behavior.
from rest_framework import generics
from .models import Item
from .serializers import ItemSerializer
class ItemListCreate(generics.ListCreateAPIView):
"""
API view to handle:
GET: Retrieve a list of all items.
POST: Create a new item.
"""
queryset = Item.objects.all()
serializer_class = ItemSerializer
class ItemRetrieveUpdateDestroy(generics.RetrieveUpdateDestroyAPIView):
"""
API view to handle a single item by its primary key (id):
GET: Retrieve one item.
PUT: Update an item.
DELETE: Destroy an item.
"""
queryset = Item.objects.all()
serializer_class = ItemSerializer
Step 5: Define URLs and Run Migrations
We need to wire up our views to URL endpoints. First, in items/urls.py (create this file):
from django.urls import path
from . import views
urlpatterns = [
path('items/', views.ItemListCreate.as_view(), name='item-list-create'),
path('items/<int:pk>/', views.ItemRetrieveUpdateDestroy.as_view(), name='item-detail'),
]
Then, include these item URLs in the main project's backend/urls.py:
from django.contrib import admin
from django.urls import path, include # <-- Don't forget to import 'include'
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('items.urls')), # <-- All our item APIs are under /api/
]
Finally, create the database tables by running migrations and start the Django development server:
python manage.py makemigrations
python manage.py migrate
python manage.py runserver
Tip: Your Django API is now live! You can visit
http://localhost:8000/api/items/in your browser or a tool like Postman. It should return an empty list[]. Keep this server running in a terminal.
Part 2: Building the React Frontend with Material UI
Now for the user-facing part! We'll create a React application that fetches data from our Django API and presents it in a beautiful, interactive Material UI table. Open a new terminal window, navigate to your main project folder (crud-table-project), and let's begin.
Step 1: Create React App and Install Dependencies
npx create-react-app frontend
cd frontend
npm install @mui/material @emotion/react @emotion/styled @mui/icons-material axios
We're installing the core Material UI library, its icons, and Axios—a popular HTTP client for making API requests more easily than with the native fetch API.
Step 2: The Main App Component and API Service
Let's create a clean service file to abstract all API calls. Create src/services/api.js:
import axios from 'axios';
// Create an Axios instance pointing to your Django backend
const apiClient = axios.create({
baseURL: 'http://localhost:8000/api', // Django server address
headers: {
'Content-Type': 'application/json',
},
});
// ItemService object containing all CRUD methods
const ItemService = {
// Get all items
getAllItems() {
return apiClient.get('/items/');
},
// Get a single item by ID
getItem(id) {
return apiClient.get(`/items/${id}/`);
},
// Create a new item
createItem(itemData) {
return apiClient.post('/items/', itemData);
},
// Update an existing item
updateItem(id, itemData) {
return apiClient.put(`/items/${id}/`, itemData);
},
// Delete an item
deleteItem(id) {
return apiClient.delete(`/items/${id}/`);
},
};
export default ItemService;
Now, let's build the core of our application in src/App.js. We'll break it down into sections. First, the imports and state setup:
import React, { useState, useEffect } from 'react';
import {
Container,
Typography,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
Button,
IconButton,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
TextField,
Box,
Snackbar,
Alert,
} from '@mui/material';
import { Edit, Delete, Add } from '@mui/icons-material';
import ItemService from './services/api';
function App() {
// State for the list of items
const [items, setItems] = useState([]);
// State for the current item being edited/created
const [currentItem, setCurrentItem] = useState({ name: '', description: '' });
// State to control the open/close of the form dialog
const [openDialog, setOpenDialog] = useState(false);
// State to determine if we are editing (true) or creating (false)
const [isEditing, setIsEditing] = useState(false);
// State for feedback alerts (success/error messages)
const [alert, setAlert] = useState({ open: false, message: '', severity: 'success' });
// Fetch items from the API when the component loads
useEffect(() => {
fetchItems();
}, []);
Step 3: Implementing CRUD Functions
Inside the App function, we define the functions that will handle our operations by calling the ItemService.
const fetchItems = async () => {
try {
const response = await ItemService.getAllItems();
setItems(response.data); // Update state with fetched items
} catch (error) {
showAlert('Failed to fetch items.', 'error');
}
};
const handleCreateOrUpdate = async () => {
try {
if (isEditing) {
// Update existing item
await ItemService.updateItem(currentItem.id, currentItem);
showAlert('Item updated successfully!', 'success');
} else {
// Create new item
await ItemService.createItem(currentItem);
showAlert('Item created successfully!', 'success');
}
// Refresh the list, close the dialog, and reset the form
fetchItems();
handleCloseDialog();
} catch (error) {
showAlert(`Operation failed: ${error.message}`, 'error');
}
};
const handleDelete = async (id) => {
if (window.confirm('Are you sure you want to delete this item?')) {
try {
await ItemService.deleteItem(id);
showAlert('Item deleted successfully!', 'success');
fetchItems(); // Refresh the list
} catch (error) {
showAlert('Failed to delete item.', 'error');
}
}
};
const handleEditClick = (item) => {
// Populate the form with the item's data for editing
setCurrentItem(item);
setIsEditing(true);
setOpenDialog(true);
};
const handleCreateClick = () => {
// Clear the form for a new item
setCurrentItem({ name: '', description: '' });
setIsEditing(false);
setOpenDialog(true);
};
const handleCloseDialog = () => {
setOpenDialog(false);
// Small delay to let dialog close before resetting state (for visual smoothness)
setTimeout(() => {
setCurrentItem({ name: '', description: '' });
setIsEditing(false);
}, 100);
};
const showAlert = (message, severity) => {
setAlert({ open: true, message, severity });
};
const handleCloseAlert = () => {
setAlert({ ...alert, open: false });
};
Step 4: Rendering the UI with Material UI Components
Finally, the return statement of our component renders the user interface. This is where Material UI shines, providing pre-styled, accessible components.
return (
<Container maxWidth="lg">
<Box sx={{ my: 4 }}>
<Typography variant="h3" component="h1" gutterBottom align="center">
Item Manager
</Typography>
<Button
variant="contained"
startIcon={<Add />}
onClick={handleCreateClick}
sx={{ mb: 3 }}
>
Add New Item
</Button>
<TableContainer component={Paper}>
<Table aria-label="simple table">
<TableHead>
<TableRow>
<TableCell><strong>ID</strong></TableCell>
<TableCell><strong>Name</strong></TableCell>
<TableCell><strong>Description</strong></TableCell>
<TableCell align="center"><strong>Actions</strong></TableCell>
</TableRow>
</TableHead>
<TableBody>
{items.map((item) => (
<TableRow key={item.id}>
<TableCell>{item.id}</TableCell>
<TableCell>{item.name}</TableCell>
<TableCell>{item.description}</TableCell>
<TableCell align="center">
<IconButton
aria-label="edit"
color="primary"
onClick={() => handleEditClick(item)}
>
<Edit />
</IconButton>
<IconButton
aria-label="delete"
color="error"
onClick={() => handleDelete(item.id)}
>
<Delete />
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Box>
{/* Dialog for Creating/Editing Items */}
<Dialog open={openDialog} onClose={handleCloseDialog}>
<DialogTitle>{isEditing ? 'Edit Item' : 'Create New Item'}</DialogTitle>
<DialogContent>
<TextField
autoFocus
margin="dense"
label="Item Name"
type="text"
fullWidth
variant="outlined"
value={currentItem.name}
onChange={(e) => setCurrentItem({ ...currentItem, name: e.target.value })}
sx={{ mb: 2 }}
/>
<TextField
margin="dense"
label="Description"
type="text"
fullWidth
multiline
rows={4}
variant="outlined"
value={currentItem.description}
onChange={(e) => setCurrentItem({ ...currentItem, description: e.target.value })}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleCloseDialog}>Cancel</Button>
<Button onClick={handleCreateOrUpdate} variant="contained">
{isEditing ? 'Update' : 'Create'}
</Button>
</DialogActions>
</Dialog>
{/* Snackbar for Alerts */}
<Snackbar
open={alert.open}
autoHideDuration={6000}
onClose={handleCloseAlert}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
>
<Alert onClose={handleCloseAlert} severity={alert.severity} sx={{ width: '100%' }}>
{alert.message}
</Alert>
</Snackbar>
</Container>
);
}
export default App;
Step 5: Start the React Development Server
Now, in your frontend directory, run:
npm start
Your default browser should open to http://localhost:3000, showcasing your fully functional CRUD table!
Congratulations! You've just built a complete full-stack application. Try adding, editing, and deleting items. Notice how the frontend React state updates and synchronizes with the backend Django database via API calls. This is the core pattern of modern web development.
Conclusion and Next Steps
In this tutorial, we've successfully built a bridge between two powerful technologies: Django for a robust, secure backend API and React with Material UI for a dynamic, user-friendly frontend. You've seen how models, serializers, and views work in Django REST Framework, and how to manage state, effects, and API calls in a React functional component.
This project is a springboard. Here are some ideas to level up your skills:
- Add Form Validation: Use Material UI's form validation or a library like Formik to ensure data quality before sending to the API.
- Implement Search & Filter: Add a search bar above the table and create a new API endpoint in Django to handle filtered queries.
- Enhance the Backend: Add user authentication with Django's
djangorestframework-simplejwtto secure your API endpoints. - Deploy It: Learn to deploy the Django backend on a platform like Railway or Heroku and the React frontend on Vercel or Netlify. Remember to update the
baseURLin your API service and configure production CORS settings.
To watch the full tutorial on YouTube, click here.
You can find the complete, organized code for this project in the accompanying GitHub repository. Feel free to clone it, experiment, and make it your own.
Found this guide helpful? Explore more web development and tech tutorials on Halogenius Ideas to continue your learning journey. Happy coding!
Leave a Reply