Spring Boot React CRUD Full Stack Application

In this tutorial, we will create a full-stack application using Spring Boot for the backend and React (using functional components and hooks) for the frontend. We will cover setting up the project, creating a Spring Boot REST API, and building a React application to consume the API.

Spring Boot React CRUD Full Stack Application Architecture

+--------------------------------------------------------+
|                       React Frontend App               |
|--------------------------------------------------------|
| +--------------+  +----------------+  +------------+   |
| |       Router |  |    Components  |  |   Services |  |
| +--------------+  +----------------+  +------------+   |
|                                                        |
|                +-----------------------------+         |
|                |       Axios HTTP Library    |         |
|                +-----------------------------+         |
+--------------------------------------------------------+
                            |
                            v
+-------------------------------------------------------+
|               Spring Boot Backend App                 |
|-------------------------------------------------------|
|              +-----------------------------+          |
|              |   Spring REST Controller    |          |
|              +-----------------------------+          |
|                 |            |         | 
|            +--------+  +--------+  +------------+     |
|            | Model  |  | Service|  | DAO (Repo) |     |
|            +--------+  +--------+  +------------+     |
+-------------------------------------------------------+
                            |
                            v
            +-----------------------------+
            |       H2 Database           |
            +-----------------------------+

Explanation:

  1. React Frontend App:

    • Router: Manages routing and navigation within the application.
    • Components: Represents the UI elements of the application.
    • Services: Handles the business logic and data processing in the frontend.
    • Axios HTTP Library: A promise-based HTTP client for making requests to the backend.
  2. Spring Boot Backend App:

    • Spring REST Controller: Handles incoming HTTP requests and defines endpoints.
    • Model: Represents the data structure or entity.
    • Service: Contains the business logic.
    • DAO (Repository): Interacts with the database.
  3. H2 Database: Stores the application's data.

In this architecture, the React frontend app communicates with the Spring Boot backend app using Axios to make HTTP requests. The backend app processes these requests, interacts with the H2 database, and sends responses back to the frontend app.

Prerequisites

Before we start, ensure you have the following:

  • Java Development Kit (JDK) installed
  • Apache Maven installed
  • Node.js and npm installed
  • An IDE (such as IntelliJ IDEA, Eclipse, or VS Code) installed
Spring Boot React CRUD Full Stack Application Architecture

Step 1: Setting Up the Spring Boot Project

1.1 Create a Spring Boot Project

  1. Open Spring Initializr:

  2. Configure Project Metadata:

    • Project: Maven Project
    • Language: Java
    • Spring Boot: Select the latest version of Spring Boot
    • Group: com.example
    • Artifact: spring-boot-react
    • Name: spring-boot-react
    • Description: Full Stack Application with Spring Boot and React
    • Package Name: com.example.springbootreact
    • Packaging: Jar
    • Java Version: 17 (or your preferred version)
    • Click Next.
  3. Select Dependencies:

    • On the Dependencies screen, select the dependencies you need. For a basic Spring Boot application, you can start with:
      • Spring Web
      • Spring Data JPA
      • H2 Database
      • Spring Boot DevTools
    • Click Next.
  4. Generate the Project:

    • Click Generate to download the project zip file.
    • Extract the zip file to your desired location.
  5. Open the Project in Your IDE:

    • Open your IDE and import the project as a Maven project.

1.2 Project Structure

After importing the project, you will see the following structure in your IDE:

spring-boot-react
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── example
│   │   │           └── springbootreact
│   │   │               ├── SpringBootReactApplication.java
│   │   │               ├── controller
│   │   │               ├── model
│   │   │               ├── repository
│   │   │               └── service
│   ├── main
│   │   └── resources
│   │       ├── application.properties
│   └── test
│       └── java
│           └── com
│               └── example
│                   └── springbootreact
│                       └── SpringBootReactApplicationTests.java
└── pom.xml

Step 2: Creating the Backend

2.1 Configure H2 Database

Open the application.properties file located in the src/main/resources directory and add the following configuration:

# H2 Database configuration
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true

# JPA settings
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

2.2 Create the Product Entity

In the model package, create a new Java class named Product:

package com.example.springbootreact.model;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String description;
    private double price;

    // Getters and Setters

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }
}

2.3 Create the ProductRepository Interface

In the repository package, create a new Java interface named ProductRepository:

package com.example.springbootreact.repository;

import com.example.springbootreact.model.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
}

2.4 Create the ProductService Interface

In the service package, create a new Java interface named ProductService:

package com.example.springbootreact.service;

import com.example.springbootreact.model.Product;

import java.util.List;
import java.util.Optional;

public interface ProductService {
    List<Product> getAllProducts();
    Optional<Product> getProductById(Long id);
    Product saveProduct(Product product);
    Product updateProduct(Product product);
    void deleteProduct(Long id);
}

2.5 Implement the ProductService Interface

In the service package, create a new Java class named ProductServiceImpl:

package com.example.springbootreact.service;

import com.example.springbootreact.model.Product;
import com.example.springbootreact.repository.ProductRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

@Service
public class ProductServiceImpl implements ProductService {

    @Autowired
    private ProductRepository productRepository;

    @Override
    public List<Product> getAllProducts() {
        return productRepository.findAll();
    }

    @Override
    public Optional<Product> getProductById(Long id) {
        return productRepository.findById(id);
    }

    @Override
    public Product saveProduct(Product product) {
        return productRepository.save(product);
    }

    @Override
    public Product updateProduct(Product product) {
        return productRepository.save(product);
    }

    @Override
    public void deleteProduct(Long id) {
        productRepository.deleteById(id);
    }
}

2.6 Create the ProductController Class

In the controller package, create a new Java class named ProductController:

package com.example.springbootreact.controller;

import com.example.springbootreact.model.Product;
import com.example.springbootreact.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Optional;

@RestController
@RequestMapping("/api/products")
public class ProductController {

    @Autowired
    private ProductService productService;

    @GetMapping
    public List<Product> getAllProducts() {
        return productService.getAllProducts();
    }

    @GetMapping("/{id}")
    public ResponseEntity<Product> getProductById(@PathVariable Long id) {
        Optional<Product> product = productService.getProductById(id);
        return product.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build());
    }

    @PostMapping
    public Product createProduct(@RequestBody Product product) {
        return productService.saveProduct(product);
    }

    @PutMapping("/{id}")
    public ResponseEntity<Product> updateProduct(@PathVariable Long id, @RequestBody Product productDetails) {
        Optional<Product> productOptional = productService.getProductById(id);
        if (productOptional.isPresent()) {
            Product product = productOptional.get();
            product.setName(productDetails.getName());
            product.setDescription(productDetails.getDescription());
            product.setPrice(productDetails.getPrice());
            return ResponseEntity.ok(productService.updateProduct(product));
        } else {
            return ResponseEntity.notFound().build();
        }
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
        Optional<Product> product = productService.getProductById(id);
        if (product.isPresent()) {
            productService.deleteProduct(id);
            return ResponseEntity.noContent().build();
        } else {
            return ResponseEntity.notFound().build();
        }
    }
}

Step 3: Creating the Frontend with React

3.1 Set Up React Project

  1. Open a terminal and navigate to your workspace directory.

  2. Create a new React project using Create React App:

    npx create-react-app react-frontend
    
  3. Navigate to the project directory:

    cd react-frontend
    

3.2 Install Axios and React Router DOM

Install Axios to make HTTP requests and React Router DOM for routing:

npm install axios react-router-dom@6

3.3 Create Components

Create the necessary components for the CRUD operations.

3.3.1 Create ProductService.js

Create a new file ProductService.js in the src directory to handle API requests:

import axios from 'axios';

const PRODUCT_API_BASE_URL = "http://localhost:8080/api/products";

class Product

Service {
    getProducts() {
        return axios.get(PRODUCT_API_BASE_URL);
    }

    createProduct(product) {
        return axios.post(PRODUCT_API_BASE_URL, product);
    }

    getProductById(productId) {
        return axios.get(`${PRODUCT_API_BASE_URL}/${productId}`);
    }

    updateProduct(product, productId) {
        return axios.put(`${PRODUCT_API_BASE_URL}/${productId}`, product);
    }

    deleteProduct(productId) {
        return axios.delete(`${PRODUCT_API_BASE_URL}/${productId}`);
    }
}

export default new ProductService();

3.3.2 Create ProductListComponent.js

Create a new file ProductListComponent.js in the src/components directory:

import React, { useState, useEffect } from 'react';
import ProductService from '../ProductService';
import { Link } from 'react-router-dom';

const ProductListComponent = () => {
    const [products, setProducts] = useState([]);

    useEffect(() => {
        ProductService.getProducts().then((res) => {
            setProducts(res.data);
        });
    }, []);

    return (
        <div>
            <h2 className="text-center">Product List</h2>
            <div className="row">
                <Link to="/add-product" className="btn btn-primary">Add Product</Link>
            </div>
            <div className="row">
                <table className="table table-striped table-bordered">
                    <thead>
                        <tr>
                            <th>Product Name</th>
                            <th>Product Description</th>
                            <th>Product Price</th>
                            <th>Actions</th>
                        </tr>
                    </thead>
                    <tbody>
                        {products.map(product => (
                            <tr key={product.id}>
                                <td>{product.name}</td>
                                <td>{product.description}</td>
                                <td>{product.price}</td>
                                <td>
                                    <Link to={`/update-product/${product.id}`} className="btn btn-info">Update</Link>
                                    <button className="btn btn-danger" onClick={() => ProductService.deleteProduct(product.id).then(() => setProducts(products.filter(p => p.id !== product.id)))}>Delete</button>
                                </td>
                            </tr>
                        ))}
                    </tbody>
                </table>
            </div>
        </div>
    );
};

export default ProductListComponent;

3.3.3 Create AddProductComponent.js

Create a new file AddProductComponent.js in the src/components directory:

import React, { useState } from 'react';
import ProductService from '../ProductService';
import { useNavigate } from 'react-router-dom';

const AddProductComponent = () => {
    const [name, setName] = useState('');
    const [description, setDescription] = useState('');
    const [price, setPrice] = useState('');
    const navigate = useNavigate();

    const saveProduct = (e) => {
        e.preventDefault();
        const product = { name, description, price };
        ProductService.createProduct(product).then(() => {
            navigate('/products');
        });
    };

    return (
        <div>
            <div className="container">
                <div className="row">
                    <div className="card col-md-6 offset-md-3 offset-md-3">
                        <h3 className="text-center">Add Product</h3>
                        <div className="card-body">
                            <form>
                                <div className="form-group">
                                    <label> Product Name: </label>
                                    <input placeholder="Name" name="name" className="form-control"
                                        value={name} onChange={(e) => setName(e.target.value)} />
                                </div>
                                <div className="form-group">
                                    <label> Product Description: </label>
                                    <input placeholder="Description" name="description" className="form-control"
                                        value={description} onChange={(e) => setDescription(e.target.value)} />
                                </div>
                                <div className="form-group">
                                    <label> Product Price: </label>
                                    <input placeholder="Price" name="price" className="form-control"
                                        value={price} onChange={(e) => setPrice(e.target.value)} />
                                </div>
                                <button className="btn btn-success" onClick={saveProduct}>Save</button>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    );
};

export default AddProductComponent;

3.3.4 Create UpdateProductComponent.js

Create a new file UpdateProductComponent.js in the src/components directory:

import React, { useState, useEffect } from 'react';
import ProductService from '../ProductService';
import { useNavigate, useParams } from 'react-router-dom';

const UpdateProductComponent = () => {
    const { id } = useParams();
    const [name, setName] = useState('');
    const [description, setDescription] = useState('');
    const [price, setPrice] = useState('');
    const navigate = useNavigate();

    useEffect(() => {
        ProductService.getProductById(id).then((res) => {
            const product = res.data;
            setName(product.name);
            setDescription(product.description);
            setPrice(product.price);
        });
    }, [id]);

    const updateProduct = (e) => {
        e.preventDefault();
        const product = { name, description, price };
        ProductService.updateProduct(product, id).then(() => {
            navigate('/products');
        });
    };

    return (
        <div>
            <div className="container">
                <div className="row">
                    <div className="card col-md-6 offset-md-3 offset-md-3">
                        <h3 className="text-center">Update Product</h3>
                        <div className="card-body">
                            <form>
                                <div className="form-group">
                                    <label> Product Name: </label>
                                    <input placeholder="Name" name="name" className="form-control"
                                        value={name} onChange={(e) => setName(e.target.value)} />
                                </div>
                                <div className="form-group">
                                    <label> Product Description: </label>
                                    <input placeholder="Description" name="description" className="form-control"
                                        value={description} onChange={(e) => setDescription(e.target.value)} />
                                </div>
                                <div className="form-group">
                                    <label> Product Price: </label>
                                    <input placeholder="Price" name="price" className="form-control"
                                        value={price} onChange={(e) => setPrice(e.target.value)} />
                                </div>
                                <button className="btn btn-success" onClick={updateProduct}>Save</button>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    );
};

export default UpdateProductComponent;

3.3.5 Create App.js

Modify the App.js file to set up routing for the application:

import React from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import ProductListComponent from './components/ProductListComponent';
import AddProductComponent from './components/AddProductComponent';
import UpdateProductComponent from './components/UpdateProductComponent';

const App = () => {
    return (
        <Router>
            <div className="container">
                <Routes>
                    <Route path="/" element={<ProductListComponent />} />
                    <Route path="/products" element={<ProductListComponent />} />
                    <Route path="/add-product" element={<AddProductComponent />} />
                    <Route path="/update-product/:id" element={<UpdateProductComponent />} />
                </Routes>
            </div>
        </Router>
    );
};

export default App;

3.3.6 Update index.js

Ensure the index.js file is set up correctly:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import 'bootstrap/dist/css/bootstrap.min.css';

ReactDOM.render(
    <React.StrictMode>
        <App />
    </React.StrictMode>,
    document.getElementById('root')
);

Step 4: Running the Application

4.1 Run the Spring Boot Application

  1. Open the SpringBootReactApplication class in the src/main/java/com/example/springbootreact directory.
  2. Click the green Run button in your IDE or use the terminal to run the application:
    ./mvnw spring-boot:run
    

4.2 Run the React Application

  1. Open a terminal and navigate to the react-frontend directory.

  2. Start the React application:

    npm start
    
  3. Open your web browser and navigate to http://localhost:3000.

You can now perform CRUD operations on the Product entity using the React frontend and Spring Boot backend.

Conclusion

In this tutorial, we created a full-stack application using Spring Boot for the backend and React (with functional components and hooks) for the frontend. We set up a REST API with Spring Boot and created a React application to consume the API. This setup provides a solid foundation for developing more complex full-stack applications with Spring Boot and React.


Comments