DEV Community

Cover image for Building Your First CRUD Application: A Step-by-Step Guide
10000coders
10000coders

Posted on

Building Your First CRUD Application: A Step-by-Step Guide

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.

  1. 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;
Enter fullscreen mode Exit fullscreen mode

  1. 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');
});
Enter fullscreen mode Exit fullscreen mode

Implementing CRUD Operations

  1. 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>
  );
};
Enter fullscreen mode Exit fullscreen mode
  1. 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>
  );
};
Enter fullscreen mode Exit fullscreen mode
  1. 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);
  }
};
Enter fullscreen mode Exit fullscreen mode
  1. 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);
  }
};
Enter fullscreen mode Exit fullscreen mode

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');
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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);
  });
});
Enter fullscreen mode Exit fullscreen mode

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)