Build a Full-Stack App with React and Spring Boot Integration

In this article, we explore how to create a full-stack application by integrating a React frontend with a Java Spring Boot backend. You'll learn the theory behind CRUD (Create, Read, Update, Delete) operations, how to set up an API in Spring Boot, and how to connect it to a React application using Axios. Step-by-step code examples are provided to demonstrate how to perform these operations, making this article an essential resource for developers looking to build efficient and scalable web applications.

Let’s break down the process of connecting a React frontend with Java Web API to perform CRUD operations. I'll provide both the theory and working code examples.

Theory
 

What is a CRUD Operation?

CRUD stands for Create, Read, Update, and Delete. These are the basic operations you can perform on data in a database.

  1. Create: Add a new record.
  2. Read: Retrieve existing records.
  3. Update: Modify an existing record.
  4. Delete: Remove a record.

Java Core Web API

A Java Core Web API allows you to expose your backend services via HTTP, making them accessible to clients like web applications, mobile apps, etc. The API endpoints correspond to the CRUD operations.

For example

  1. POST /api/items: Create a new item.
  2. GET /api/items: Retrieve all items.
  3. GET /api/items/{id}: Retrieve a specific item by ID.
  4. PUT /api/items/{id}: Update an existing item by ID.
  5. DELETE /api/items/{id}: Delete an item by ID.

React Frontend

React is a JavaScript library used to build user interfaces. It can interact with your API using HTTP requests (e.g., using fetch or Axios).

Spring Boot Setup

First, ensure you have a Spring Boot project set up. You can create a new Spring Boot project using Spring Initializr (https://start.spring.io/) with dependencies like Spring Web and Spring Data JPA.

Java Model

In your Java project, create an Item model.

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

@Entity
public class Item {

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

    // 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;
    }
}

Java Repository

Create a repository interface for Item.

import org.springframework.data.jpa.repository.JpaRepository;

public interface ItemRepository extends JpaRepository<Item, Long> {
}

Java Controller

Create a controller to handle the CRUD operations.

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/items")
public class ItemController {

    @Autowired
    private ItemRepository itemRepository;

    @GetMapping
    public List<Item> getItems() {
        return itemRepository.findAll();
    }

    @GetMapping("/{id}")
    public ResponseEntity<Item> getItem(@PathVariable Long id) {
        Optional<Item> item = itemRepository.findById(id);
        if (item.isPresent()) {
            return ResponseEntity.ok(item.get());
        } else {
            return ResponseEntity.notFound().build();
        }
    }

    @PostMapping
    public Item createItem(@RequestBody Item item) {
        return itemRepository.save(item);
    }

    @PutMapping("/{id}")
    public ResponseEntity<Item> updateItem(@PathVariable Long id, @RequestBody Item itemDetails) {
        Optional<Item> item = itemRepository.findById(id);
        if (item.isPresent()) {
            Item existingItem = item.get();
            existingItem.setName(itemDetails.getName());
            return ResponseEntity.ok(itemRepository.save(existingItem));
        } else {
            return ResponseEntity.notFound().build();
        }
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteItem(@PathVariable Long id) {
        Optional<Item> item = itemRepository.findById(id);
        if (item.isPresent()) {
            itemRepository.delete(item.get());
            return ResponseEntity.noContent().build();
        } else {
            return ResponseEntity.notFound().build();
        }
    }
}

Database Configuration

In your application.properties or application.yml file, configure your database connection (e.g., using H2 for simplicity):

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

Running the Spring Boot Application

To run the Spring Boot application.

./mvnw spring-boot:run

React Frontend Setup
 

Setting Up React Project

npx create-react-app my-app
cd my-app
npm install axios

Creating an API Service in React

Create a file src/apiService.js.

import axios from 'axios';

const API_URL = 'http://localhost:8080/api/items';

export const getItems = async () => {
  return await axios.get(API_URL);
};

export const getItemById = async (id) => {
  return await axios.get(`${API_URL}/${id}`);
};

export const createItem = async (data) => {
  return await axios.post(API_URL, data);
};

export const updateItem = async (id, data) => {
  return await axios.put(`${API_URL}/${id}`, data);
};

export const deleteItem = async (id) => {
  return await axios.delete(`${API_URL}/${id}`);
};

Implementing CRUD Operations in React

In src/App.js.

import React, { useState, useEffect } from 'react';
import { getItems, createItem, updateItem, deleteItem } from './apiService';

function App() {
  const [items, setItems] = useState([]);
  const [newItem, setNewItem] = useState('');

  useEffect(() => {
    loadItems();
  }, []);

  const loadItems = async () => {
    const response = await getItems();
    setItems(response.data);
  };

  const handleCreate = async () => {
    if (newItem.trim()) {
      await createItem({ name: newItem });
      setNewItem('');
      loadItems();
    }
  };

  const handleUpdate = async (id) => {
    const updatedName = prompt("Enter new name:");
    if (updatedName) {
      await updateItem(id, { name: updatedName });
      loadItems();
    }
  };

  const handleDelete = async (id) => {
    if (window.confirm("Are you sure you want to delete this item?")) {
      await deleteItem(id);
      loadItems();
    }
  };

  return (
    <div>
      <h1>Items</h1>
      <ul>
        {items.map(item => (
          <li key={item.id}>
            {item.name}
            <button onClick={() => handleUpdate(item.id)}>Update</button>
            <button onClick={() => handleDelete(item.id)}>Delete</button>
          </li>
        ))}
      </ul>
      <input 
        type="text" 
        value={newItem} 
        onChange={(e) => setNewItem(e.target.value)} 
        placeholder="Enter new item name" 
      />
      <button onClick={handleCreate}>Add Item</button>
    </div>
  );
}

export default App;

Running the Application

Run the ASP.NET Core Web API.

Ensure your API is running on http://localhost:5000.

Run the React App.

npm start

The React app should now be running on http://localhost:3000.

Handling CORS (if necessary)
 

CORS Configuration

If you encounter CORS issues, make sure your Java API is configured to allow requests from your React app. In Spring Boot, you can configure CORS globally using a WebMvcConfigurer bean. Spring Boot automatically sets up the necessary routing and controller mappings.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                        .allowedOrigins("*")
                        .allowedMethods("*")
                        .allowedHeaders("*");
            }
        };
    }
}

Testing

You can now perform all CRUD operations (Create, Read, Update, Delete) from the React frontend, and they should interact with the Java Web API.

This setup provides a full cycle of data operations from the frontend to the backend. Feel free to modify and expand on this foundation as per your requirements!

Conclusion

In this article, we’ve demonstrated how to build a full-stack application by integrating a React frontend with a Java Spring Boot backend to perform seamless CRUD operations. By following the steps outlined, you’ve learned how to,

  1. Set up a Spring Boot project to create a RESTful API.
  2. Implement CRUD operations in a Spring Boot controller.
  3. Connect your React frontend to the Spring Boot backend using Axios to manage data.
  4. Handle asynchronous requests and update the UI in real-time.

This powerful combination of React and Spring Boot provides a robust foundation for developing scalable and maintainable web applications. Whether you're managing simple data or building more complex applications, understanding how to perform CRUD operations across a frontend and backend is a crucial skill in full-stack development.

With this knowledge, you’re now equipped to expand your application further—perhaps by adding user authentication, enhancing your UI, or deploying your application to a production environment. The possibilities are endless, and this article is just the beginning of your journey into full-stack development with React and Spring Boot.

Happy coding!


Similar Articles