Building Your First CRUD Application: A Step-by-Step Guide
Learn how to create a complete CRUD (Create, Read, Update, Delete) application from scratch using modern web technologies.
Understanding CRUD Operations
CRUD represents the four basic operations of persistent storage:
Create: Adding new records
Read: Retrieving existing records
Update: Modifying existing records
Delete: Removing records
Project Setup
Let's create a simple Task Management application using React and Node.js.
- Frontend Setup
// App.js
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function App() {
const [tasks, setTasks] = useState([]);
const [newTask, setNewTask] = useState('');
useEffect(() => {
fetchTasks();
}, []);
const fetchTasks = async () => {
try {
const response = await axios.get('http://localhost:5000/api/tasks');
setTasks(response.data);
} catch (error) {
console.error('Error fetching tasks:', error);
}
};
return (
<div className="container mx-auto p-4">
<h1 className="text-2xl font-bold mb-4">Task Manager</h1>
{/* Task form and list will go here */}
</div>
);
}
export default App;
- Backend Setup
// server.js
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors());
app.use(express.json());
let tasks = [];
// Create
app.post('/api/tasks', (req, res) => {
const task = {
id: Date.now(),
title: req.body.title,
completed: false
};
tasks.push(task);
res.status(201).json(task);
});
// Read
app.get('/api/tasks', (req, res) => {
res.json(tasks);
});
// Update
app.put('/api/tasks/:id', (req, res) => {
const task = tasks.find(t => t.id === parseInt(req.params.id));
if (!task) return res.status(404).json({ message: 'Task not found' });
task.title = req.body.title;
task.completed = req.body.completed;
res.json(task);
});
// Delete
app.delete('/api/tasks/:id', (req, res) => {
const taskIndex = tasks.findIndex(t => t.id === parseInt(req.params.id));
if (taskIndex === -1) return res.status(404).json({ message: 'Task not found' });
tasks.splice(taskIndex, 1);
res.status(204).send();
});
app.listen(5000, () => {
console.log('Server running on port 5000');
});
Implementing CRUD Operations
- Create Operation
// TaskForm.js
const TaskForm = ({ onAddTask }) => {
const [title, setTitle] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
try {
const response = await axios.post('http://localhost:5000/api/tasks', {
title
});
onAddTask(response.data);
setTitle('');
} catch (error) {
console.error('Error creating task:', error);
}
};
return (
<form onSubmit={handleSubmit} className="mb-4">
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Add new task"
className="border p-2 mr-2"
/>
<button type="submit" className="bg-blue-500 text-white px-4 py-2 rounded">
Add Task
</button>
</form>
);
};
- Read Operation
// TaskList.js
const TaskList = ({ tasks, onUpdateTask, onDeleteTask }) => {
return (
<div className="space-y-2">
{tasks.map(task => (
<div key={task.id} className="flex items-center justify-between p-2 border">
<span className={task.completed ? 'line-through' : ''}>
{task.title}
</span>
<div className="space-x-2">
<button
onClick={() => onUpdateTask(task.id, { ...task, completed: !task.completed })}
className="text-green-500"
>
{task.completed ? 'Undo' : 'Complete'}
</button>
<button
onClick={() => onDeleteTask(task.id)}
className="text-red-500"
>
Delete
</button>
</div>
</div>
))}
</div>
);
};
- Update Operation
// Update task function
const updateTask = async (id, updatedTask) => {
try {
const response = await axios.put(`http://localhost:5000/api/tasks/${id}`, updatedTask);
setTasks(tasks.map(task =>
task.id === id ? response.data : task
));
} catch (error) {
console.error('Error updating task:', error);
}
};
- Delete Operation
// Delete task function
const deleteTask = async (id) => {
try {
await axios.delete(`http://localhost:5000/api/tasks/${id}`);
setTasks(tasks.filter(task => task.id !== id));
} catch (error) {
console.error('Error deleting task:', error);
}
};
Adding Error Handling
// Error handling component
const ErrorMessage = ({ message }) => {
return (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
{message}
</div>
);
};
// Usage in App.js
const [error, setError] = useState(null);
// In your API calls
try {
// API call
} catch (error) {
setError(error.response?.data?.message || 'An error occurred');
}
Adding Loading States
// Loading state component
const LoadingSpinner = () => {
return (
<div className="flex justify-center items-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
</div>
);
};
// Usage in App.js
const [loading, setLoading] = useState(false);
// In your API calls
setLoading(true);
try {
// API call
} finally {
setLoading(false);
}
Best Practices
State Management
Use proper state management for larger applications
Consider using Redux or Context API
Keep state as local as possible
Error Handling
Implement proper error boundaries
Show user-friendly error messages
Log errors for debugging
Performance
Implement pagination for large datasets
Use proper indexing in the database
Optimize API calls
Security
Implement proper authentication
Validate input data
Use HTTPS
Implement rate limiting
Testing Your CRUD Application
// Example test using Jest
describe('Task API', () => {
test('should create a new task', async () => {
const response = await axios.post('http://localhost:5000/api/tasks', {
title: 'Test Task'
});
expect(response.status).toBe(201);
expect(response.data.title).toBe('Test Task');
});
test('should get all tasks', async () => {
const response = await axios.get('http://localhost:5000/api/tasks');
expect(response.status).toBe(200);
expect(Array.isArray(response.data)).toBe(true);
});
});
Conclusion
Building a CRUD application is a fundamental skill for web developers. This guide has covered:
Setting up a full-stack application
Implementing all CRUD operations
Adding error handling and loading states
Following best practices
Testing the application
Remember to:
Keep your code organized
Follow security best practices
Write tests for your application
Handle errors gracefully
Consider scalability from the start
Next Steps
Add user authentication
Implement a proper database
Add more features like:
Task categories
Due dates
Priority levels
Search functionality
Resources
React Documentation
Express.js Documentation
MongoDB Documentation
Jest Documentation
Citations
React Documentation - State and Lifecycle
Express.js - API Reference
MongoDB - CRUD Operations
Jest - Testing Framework
Top comments (0)