How to Build a CRUD App with React 18 and Node.js 20

In this guide, we will create a contact management system. The backend will be powered by Node.js and Express.js, and the frontend will use React 18. The app will allow users to add, view, edit, and delete contacts.


Prerequisites

Before getting started, ensure you have:

  • Node.js v20.24 installed (check with node -v)
  • React 18 familiarity
  • Basic knowledge of REST APIs and Express.js

1. Setting up the Backend (Node.js + Express.js)

Step 1: Initialize a new Node.js project

First, create a directory for your project and initialize it with npm:

mkdir contact-manager
cd contact-manager
npm init -y

Step 2: Install required packages

We need Express.js for the server and mongoose for interacting with MongoDB. Install these dependencies:

npm install express mongoose cors

We also need nodemon for auto-reloading during development:

npm install --save-dev nodemon

Update the package.json to include a start script:

"scripts": {
  "start": "nodemon index.js"
}

Step 3: Set up the Express server

Create a file named index.js and set up a simple server with Express:

const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');

const app = express();
app.use(express.json());  // for parsing application/json
app.use(cors());  // to enable CORS

// Connect to MongoDB
mongoose.connect('mongodb://localhost:27017/contact-manager', {
  useNewUrlParser: true,
  useUnifiedTopology: true,
})
.then(() => console.log('MongoDB connected'))
.catch((err) => console.log(err));

// Start the server
const PORT = 5000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

This sets up the basic Express server that listens on port 5000 and connects to a MongoDB database named contact-manager.

Step 4: Define the Contact model

Now, we’ll define a mongoose model to represent our contact schema. Create a models folder and inside it, create a file called Contact.js:

const mongoose = require('mongoose');

const ContactSchema = new mongoose.Schema({
  name: { type: String, required: true },
  email: { type: String, required: true },
  phone: { type: String, required: true },
});

module.exports = mongoose.model('Contact', ContactSchema);

Step 5: Create API routes for CRUD operations

Next, define the API routes for the CRUD operations. Update index.js as follows:

const Contact = require('./models/Contact');

// Create a new contact
app.post('/contacts', async (req, res) => {
  try {
    const contact = new Contact(req.body);
    await contact.save();
    res.status(201).json(contact);
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

// Get all contacts
app.get('/contacts', async (req, res) => {
  try {
    const contacts = await Contact.find();
    res.json(contacts);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// Get a single contact by ID
app.get('/contacts/:id', async (req, res) => {
  try {
    const contact = await Contact.findById(req.params.id);
    res.json(contact);
  } catch (err) {
    res.status(404).json({ error: 'Contact not found' });
  }
});

// Update a contact by ID
app.put('/contacts/:id', async (req, res) => {
  try {
    const contact = await Contact.findByIdAndUpdate(req.params.id, req.body, { new: true });
    res.json(contact);
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

// Delete a contact by ID
app.delete('/contacts/:id', async (req, res) => {
  try {
    await Contact.findByIdAndDelete(req.params.id);
    res.json({ message: 'Contact deleted' });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

Now, our backend is fully set up!


2. Setting Up the Frontend (React 18)

Step 1: Create a new React app

In the project root, create a new React app using the latest version (React 18):

npx create-react-app client
cd client

Step 2: Install Axios for API requests

To interact with our backend API, install Axios:

npm install axios

Step 3: Create Components for Contact Management

Inside the src folder, create a components folder to house our React components. We’ll create three components:

  1. ContactList – to display all contacts
  2. AddContact – to add a new contact
  3. EditContact – to edit an existing contact
ContactList.js
import React, { useEffect, useState } from 'react';
import axios from 'axios';

const ContactList = () => {
  const [contacts, setContacts] = useState([]);

  useEffect(() => {
    axios.get('http://localhost:5000/contacts')
      .then(res => setContacts(res.data))
      .catch(err => console.error(err));
  }, []);

  return (
    <div>
      <h2>Contact List</h2>
      {contacts.map(contact => (
        <div key={contact._id}>
          <p>{contact.name} - {contact.phone}</p>
        </div>
      ))}
    </div>
  );
}

export default ContactList;
AddContact.js
import React, { useState } from 'react';
import axios from 'axios';

const AddContact = () => {
  const [contact, setContact] = useState({ name: '', email: '', phone: '' });

  const handleChange = (e) => {
    setContact({ ...contact, [e.target.name]: e.target.value });
  }

  const handleSubmit = (e) => {
    e.preventDefault();
    axios.post('http://localhost:5000/contacts', contact)
      .then(res => console.log('Contact added:', res.data))
      .catch(err => console.error(err));
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" name="name" placeholder="Name" onChange={handleChange} />
      <input type="email" name="email" placeholder="Email" onChange={handleChange} />
      <input type="text" name="phone" placeholder="Phone" onChange={handleChange} />
      <button type="submit">Add Contact</button>
    </form>
  );
}

export default AddContact;
EditContact.js
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { useParams } from 'react-router-dom';

const EditContact = () => {
  const [contact, setContact] = useState({ name: '', email: '', phone: '' });
  const { id } = useParams();

  useEffect(() => {
    axios.get(`http://localhost:5000/contacts/${id}`)
      .then(res => setContact(res.data))
      .catch(err => console.error(err));
  }, [id]);

  const handleChange = (e) => {
    setContact({ ...contact, [e.target.name]: e.target.value });
  }

  const handleSubmit = (e) => {
    e.preventDefault();
    axios.put(`http://localhost:5000/contacts/${id}`, contact)
      .then(res => console.log('Contact updated:', res.data))
      .catch(err => console.error(err));
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" name="name" value={contact.name} onChange={handleChange} />
      <input type="email" name="email" value={contact.email} onChange={handleChange} />
      <input type="text" name="phone" value={contact.phone} onChange={handleChange} />
      <button type="submit">Update Contact</button>
    </form>
  );
}

export default EditContact;

Step 4: Set up React Router

To navigate between the components, install React Router:

npm install react-router-dom

Update src/App.js to include routing:

import React from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import ContactList from './components/ContactList';
import AddContact from './components/AddContact';
import EditContact from './components/EditContact';

function App() {
  return (
    <Router>
      <Routes>
        <Route path="/" element={<ContactList />} />
        <Route path="/add" element={<AddContact />} />
        <Route path="/edit/:

id" element={<EditContact />} />
      </Routes>
    </Router>
  );
}

export default App;

Conclusion

You’ve now built a CRUD application with React 18 and Node.js 20.24. To summarize:

  • We built a backend API using Express.js and MongoDB.
  • We created a React frontend to manage contact records.
  • Axios was used for API requests, and React Router for navigation.

Feel free to extend this application by adding features like validation, pagination, or search functionality!

Scroll to Top