Building a CRUD App with React and Node.js
In this tutorial, we will be building a CRUD (Create, Read, Update, Delete) application using React for the frontend and Node.js for the backend. We will start by setting up the development environment, creating the necessary database, building the backend API endpoints, and finally creating the frontend components. By the end of this tutorial, you will have a fully functional CRUD app that allows users to perform basic CRUD operations on a database.
Introduction
What is a CRUD App
A CRUD app is an application that allows users to create, read, update, and delete data. These are the basic operations that most applications require when interacting with a database. In this tutorial, we will be building a simple CRUD app that allows users to manage a list of products.
Why use React and Node.js
React is a popular JavaScript library for building user interfaces. It allows developers to build reusable UI components and provides a virtual DOM that efficiently updates the UI when the underlying data changes. Node.js, on the other hand, is a JavaScript runtime that allows developers to build scalable and efficient server-side applications. By using React for the frontend and Node.js for the backend, we can build a full-stack JavaScript application with a consistent programming language and toolset.
Overview of the tutorial
In this tutorial, we will start by setting up the development environment. We will install Node.js, create a new React project using Create React App, and set up the server using Express. Next, we will create the necessary database for our app. We will choose a database, set it up, and create the required tables. Once the database is set up, we will build the backend API endpoints. We will implement the CRUD operations for managing the products and handle errors and validation. Finally, we will build the frontend components. We will create the necessary components, implement data fetching from the backend, and handle user input.
Setting up the Development Environment
Installing Node.js
Before we can start building our CRUD app, we need to install Node.js. Node.js can be downloaded from the official website (https://nodejs.org) and comes with npm, the package manager for Node.js. Once Node.js is installed, we can verify the installation by running the following command in the terminal:
node -v
This should print the version of Node.js installed on your machine.
Creating a new React project
Next, let's create a new React project using Create React App. Create React App is a tool that sets up a new React project with all the necessary configuration and dependencies. To create a new React project, run the following command in the terminal:
npx create-react-app crud-app
This will create a new directory called crud-app
with the basic project structure and files. Change into the crud-app
directory by running the following command:
cd crud-app
Setting up the server
Now that we have our React project set up, let's set up the server using Express. Express is a minimal and flexible Node.js web application framework that provides a set of robust features for building web applications. To set up the server, we first need to install Express. Run the following command in the terminal:
npm install express
Once Express is installed, create a new file called server.js
in the root directory of your project. Inside server.js
, add the following code:
const express = require('express');
const app = express();
const port = 3000;
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(port, () => {
console.log(`Server listening at http://localhost:${port}`);
});
This code sets up a simple Express server that listens on port 3000. When a request is made to the root URL (/
), it responds with "Hello World!". To start the server, run the following command in the terminal:
node server.js
You should see the message "Server listening at http://localhost:3000" printed in the terminal. You can now access the server at http://localhost:3000 in your browser and see the "Hello World!" message.
Creating the Database
Choosing a database
Before we can start building the backend API endpoints, we need to choose a database. There are many different databases available, each with its own strengths and weaknesses. For this tutorial, we will be using PostgreSQL, a popular open-source relational database management system. PostgreSQL is known for its stability, scalability, and extensibility.
To install PostgreSQL, visit the official website (https://www.postgresql.org) and follow the installation instructions for your operating system.
Setting up the database
Once PostgreSQL is installed, we can create a new database for our app. Open a terminal and run the following command to open the PostgreSQL command-line interface:
psql
Inside the PostgreSQL command-line interface, run the following command to create a new database called crud_app
:
CREATE DATABASE crud_app;
You can verify that the database was created successfully by running the following command:
\l
This will list all the databases on your PostgreSQL server, and you should see crud_app
in the list.
Creating the necessary tables
Now that we have our database set up, let's create the necessary tables for our app. We will be creating a products
table to store the products. Inside the PostgreSQL command-line interface, run the following command to connect to the crud_app
database:
\c crud_app
Once connected to the database, run the following command to create the products
table:
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
price NUMERIC(10, 2) NOT NULL,
description TEXT
);
This command creates a table called products
with four columns: id
, name
, price
, and description
. The id
column is an auto-incrementing primary key, the name
column is a string, the price
column is a numeric value with two decimal places, and the description
column is a text field.
You can verify that the table was created successfully by running the following command:
\d
This will list all the tables in the crud_app
database, and you should see products
in the list.
Building the Backend
Creating API endpoints
Now that our database is set up, let's build the backend API endpoints for our CRUD app. We will be using Express to handle the HTTP requests and interact with the database. Inside server.js
, add the following code:
const express = require('express');
const app = express();
const port = 3000;
app.use(express.json());
app.get('/api/products', (req, res) => {
// Code to get all products from the database
});
app.get('/api/products/:id', (req, res) => {
// Code to get a single product by id from the database
});
app.post('/api/products', (req, res) => {
// Code to create a new product in the database
});
app.put('/api/products/:id', (req, res) => {
// Code to update a product by id in the database
});
app.delete('/api/products/:id', (req, res) => {
// Code to delete a product by id from the database
});
app.listen(port, () => {
console.log(`Server listening at http://localhost:${port}`);
});
This code sets up the API endpoints for getting all products, getting a single product by id, creating a new product, updating a product by id, and deleting a product by id. We use the Express get
, post
, put
, and delete
methods to define the routes and provide the corresponding callback functions to handle the requests.
Implementing CRUD operations
Now let's implement the CRUD operations inside the API endpoints. Inside server.js
, add the following code:
const express = require('express');
const app = express();
const port = 3000;
app.use(express.json());
app.get('/api/products', (req, res) => {
// Code to get all products from the database
// ...
const products = await db.query('SELECT * FROM products');
res.json(products.rows);
});
app.get('/api/products/:id', (req, res) => {
// Code to get a single product by id from the database
// ...
const product = await db.query('SELECT * FROM products WHERE id = $1', [req.params.id]);
res.json(product.rows[0]);
});
app.post('/api/products', (req, res) => {
// Code to create a new product in the database
// ...
const { name, price, description } = req.body;
const product = await db.query('INSERT INTO products (name, price, description) VALUES ($1, $2, $3) RETURNING *', [name, price, description]);
res.json(product.rows[0]);
});
app.put('/api/products/:id', (req, res) => {
// Code to update a product by id in the database
// ...
const { name, price, description } = req.body;
const product = await db.query('UPDATE products SET name = $1, price = $2, description = $3 WHERE id = $4 RETURNING *', [name, price, description, req.params.id]);
res.json(product.rows[0]);
});
app.delete('/api/products/:id', (req, res) => {
// Code to delete a product by id from the database
// ...
const product = await db.query('DELETE FROM products WHERE id = $1 RETURNING *', [req.params.id]);
res.json(product.rows[0]);
});
app.listen(port, () => {
console.log(`Server listening at http://localhost:${port}`);
});
This code uses the db
object to query the database and perform the CRUD operations. We use the await
keyword to wait for the database queries to complete and then send the results back as JSON responses.
Handling errors and validation
It's important to handle errors and perform validation when building a CRUD app. Let's add error handling and validation to our API endpoints. Inside server.js
, add the following code:
const express = require('express');
const app = express();
const port = 3000;
app.use(express.json());
app.get('/api/products', (req, res) => {
try {
const products = await db.query('SELECT * FROM products');
res.json(products.rows);
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Internal server error' });
}
});
app.get('/api/products/:id', (req, res) => {
try {
const product = await db.query('SELECT * FROM products WHERE id = $1', [req.params.id]);
if (product.rows.length === 0) {
res.status(404).json({ error: 'Product not found' });
} else {
res.json(product.rows[0]);
}
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Internal server error' });
}
});
app.post('/api/products', (req, res) => {
try {
const { name, price, description } = req.body;
if (!name || !price) {
res.status(400).json({ error: 'Name and price are required' });
return;
}
const product = await db.query('INSERT INTO products (name, price, description) VALUES ($1, $2, $3) RETURNING *', [name, price, description]);
res.json(product.rows[0]);
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Internal server error' });
}
});
app.put('/api/products/:id', (req, res) => {
try {
const { name, price, description } = req.body;
if (!name || !price) {
res.status(400).json({ error: 'Name and price are required' });
return;
}
const product = await db.query('UPDATE products SET name = $1, price = $2, description = $3 WHERE id = $4 RETURNING *', [name, price, description, req.params.id]);
if (product.rows.length === 0) {
res.status(404).json({ error: 'Product not found' });
} else {
res.json(product.rows[0]);
}
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Internal server error' });
}
});
app.delete('/api/products/:id', (req, res) => {
try {
const product = await db.query('DELETE FROM products WHERE id = $1 RETURNING *', [req.params.id]);
if (product.rows.length === 0) {
res.status(404).json({ error: 'Product not found' });
} else {
res.json(product.rows[0]);
}
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Internal server error' });
}
});
app.listen(port, () => {
console.log(`Server listening at http://localhost:${port}`);
});
This code adds try-catch blocks around the database queries to catch any errors that may occur. If an error occurs, we log it to the console and send an appropriate error response with a status code.
Building the Frontend
Creating components
Now that the backend is complete, let's move on to building the frontend components using React. Inside the src
directory of your React project, create a new directory called components
. Inside the components
directory, create a new file called ProductList.js
. Inside ProductList.js
, add the following code:
import React, { useEffect, useState } from 'react';
const ProductList = () => {
const [products, setProducts] = useState([]);
useEffect(() => {
fetch('/api/products')
.then((response) => response.json())
.then((data) => setProducts(data))
.catch((error) => console.error(error));
}, []);
return (
<ul>
{products.map((product) => (
<li key={product.id}>
{product.name} - {product.price}
</li>
))}
</ul>
);
};
export default ProductList;
This code defines a functional component called ProductList
. Inside the component, we use the useState
hook to create a state variable products
and a setter function setProducts
. We use the useEffect
hook to fetch the list of products from the backend API when the component is mounted. We then map over the products
array and render a list item for each product.
Implementing data fetching
Now let's use the ProductList
component to fetch and display the list of products. Inside the src
directory of your React project, open the App.js
file and replace the existing code with the following:
import React from 'react';
import ProductList from './components/ProductList';
const App = () => {
return (
<div>
<h1>CRUD App with React and Node.js</h1>
<ProductList />
</div>
);
};
export default App;
This code imports the ProductList
component and renders it inside the App
component. When you run the app, you should see the heading "CRUD App with React and Node.js" and a list of products fetched from the backend API.
Handling user input
Now let's add a form to create new products. Inside the components
directory, create a new file called ProductForm.js
. Inside ProductForm.js
, add the following code:
import React, { useState } from 'react';
const ProductForm = () => {
const [name, setName] = useState('');
const [price, setPrice] = useState('');
const [description, setDescription] = useState('');
const handleSubmit = (event) => {
event.preventDefault();
const product = { name, price, description };
fetch('/api/products', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(product),
})
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.error(error));
};
return (
<form onSubmit={handleSubmit}>
<label>
Name:
<input type="text" value={name} onChange={(event) => setName(event.target.value)} />
</label>
<label>
Price:
<input type="number" value={price} onChange={(event) => setPrice(event.target.value)} />
</label>
<label>
Description:
<textarea value={description} onChange={(event) => setDescription(event.target.value)} />
</label>
<button type="submit">Create</button>
</form>
);
};
export default ProductForm;
This code defines a functional component called ProductForm
. Inside the component, we use the useState
hook to create state variables for the name
, price
, and description
fields. We use the handleSubmit
function to handle the form submission. When the form is submitted, we create a new product object with the form values and send a POST request to the backend API to create the product.
To use the ProductForm
component, open the App.js
file and add the following code inside the div
element:
<ProductForm />
When you run the app, you should see a form with input fields for the name, price, and description, and a "Create" button.