Introduction:
Welcome to the “Book Management System: A Full-Stack CRUD Application with Spring Boot and React” tutorial! In this tutorial, you’ll learn how to build a full-stack web application for managing books using Spring Boot for the backend and React for the frontend.
Throughout this tutorial, you’ll be guided step by step on how to set up your development environment, create a backend RESTful API with Spring Boot, develop a frontend UI with React, and connect them together to enable CRUD operations (Create, Read, Update, Delete) on books.
By the end of this tutorial, you’ll have a fully functional web application where users can view, add, update, and delete books through an intuitive user interface.
Prerequisites:
- Basic knowledge of Java, JavaScript, and React.
- JDK (Java Development Kit) installed on your machine.
- Node.js and npm (Node Package Manager) installed.
- IDE (Integrated Development Environment) like IntelliJ IDEA or Eclipse.
Step 1: Set Up the Backend with Spring Boot
Create a new Spring Boot Project:
You can use Spring Initializr to create a new Spring Boot project. Include dependencies for Spring Web, Spring Data JPA, and H2 Database (for simplicity).
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<!-- Spring Web for RESTful APIs -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Data JPA for Data Access -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- H2 Database for In-memory Database -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Spring Boot DevTools for Development -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- Spring Boot Test for Testing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
src
├── main
│ ├── java
│ │ └── com
│ │ └── example
│ │ └── demo
│ │ ├── controller
│ │ │ └── BookController.java
│ │ ├── model
│ │ │ └── Book.java
│ │ ├── repository
│ │ │ └── BookRepository.java
│ │ └── DemoApplication.java
│ └── resources
│ └── application.properties
└── test
└── java
└── com
└── example
└── demo
└── DemoApplicationTests.java
Define Entity Class:
Create a class to represent your entity (e.g., Book
). Annotate the class with @Entity
and define necessary fields and relationships.
package com.example.demo.model;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String author;
private int year;
// Constructors, getters, and setters
}
Create Repository Interface:
Create a repository interface by extending JpaRepository
interface provided by Spring Data JPA. This interface will handle database operations.
package com.example.demo.repository;
import com.example.demo.model.Book;
import org.springframework.data.jpa.repository.JpaRepository;
public interface BookRepository extends JpaRepository<Book, Long> {}
Implement Controller:
Create a REST controller class to handle HTTP requests. Define methods for CRUD operations using @GetMapping
, @PostMapping
, @PutMapping
, and @DeleteMapping
annotations.
package com.example.demo.controller;
import com.example.demo.model.Book;
import com.example.demo.repository.BookRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/books")
public class BookController {
@Autowired
private BookRepository bookRepository;
@GetMapping
public List<Book> getAllBooks() {
return bookRepository.findAll();
}
@PostMapping
public Book createBook(@RequestBody Book book) {
return bookRepository.save(book);
}
@PutMapping("/{id}")
public Book updateBook(@PathVariable Long id, @RequestBody Book bookDetails) {
Book book = bookRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Book not found with id: " + id));
book.setTitle(bookDetails.getTitle());
book.setAuthor(bookDetails.getAuthor());
book.setYear(bookDetails.getYear());
return bookRepository.save(book);
}
@DeleteMapping("/{id}")
public void deleteBook(@PathVariable Long id) {
bookRepository.deleteById(id);
}
}
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
Run the Application:
Run your Spring Boot application. It should start on a default port (usually 8080) and be ready to receive requests.
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Step 2: Set Up the Frontend with React
Create a React App:
Use create-react-app
to bootstrap a new React application. Open your terminal and run:
npx create-react-app my-crud-app
Install Axios:
Axios is a promise-based HTTP client for the browser and Node.js. It will be used to make HTTP requests to the backend. Install it by running:
npm install axios
Create Components:
Create components for listing, adding, updating, and deleting entities (e.g., BookList
, AddBook
, EditBook
). Define the UI and functionality for each component.
my-crud-app
├── src
│ ├── components
│ │ ├── AddBook.js
│ │ ├── Book.js
│ │ ├── BookList.js
│ │ └── EditBook.js
│ ├── services
│ │ └── BookService.js
│ ├── App.css
│ ├── App.js
│ ├── index.css
│ └── index.js
└── package.json
import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import BookList from './components/BookList';
import AddBook from './components/AddBook';
import EditBook from './components/EditBook';
function App() {
return (
<Router>
<div className="App">
<Switch>
<Route exact path="/" component={BookList} />
<Route exact path="/add" component={AddBook} />
<Route exact path="/edit/:id" component={EditBook} />
</Switch>
</div>
</Router>
);
}
export default App;
Fetch Data from Backend:
Use Axios to fetch data from the backend. In your React components, make GET requests to the appropriate backend endpoints to retrieve data.
import React, { useEffect, useState } from 'react';
import BookService from '../services/BookService';
import { Link } from 'react-router-dom';
const BookList = () => {
const [books, setBooks] = useState([]);
useEffect(() => {
fetchBooks();
}, []);
const fetchBooks = async () => {
const response = await BookService.getAllBooks();
setBooks(response.data);
};
const deleteBook = async (id) => {
await BookService.deleteBook(id);
fetchBooks();
};
return (
<div>
<h2>Books</h2>
<Link to="/add">Add Book</Link>
<table>
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>Year</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{books.map((book) => (
<tr key={book.id}>
<td>{book.title}</td>
<td>{book.author}</td>
<td>{book.year}</td>
<td>
<Link to={`/edit/${book.id}`}>Edit</Link>
<button onClick={() => deleteBook(book.id)}>Delete</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
export default BookList;
import React, { useState } from 'react';
import BookService from '../services/BookService';
import { useHistory } from 'react-router-dom';
const AddBook = () => {
const [title, setTitle] = useState('');
const [author, setAuthor] = useState('');
const [year, setYear] = useState('');
const history = useHistory();
const handleSubmit = async (e) => {
e.preventDefault();
await BookService.addBook({ title, author, year });
history.push('/');
};
return (
<div>
<h2>Add Book</h2>
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Title"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<input
type="text"
placeholder="Author"
value={author}
onChange={(e) => setAuthor(e.target.value)}
/>
<input
type="text"
placeholder="Year"
value={year}
onChange={(e) => setYear(e.target.value)}
/>
<button type="submit">Add</button>
</form>
</div>
);
};
export default AddBook;
import React, { useEffect, useState } from 'react';
import { useParams, useHistory } from 'react-router-dom';
import BookService from '../services/BookService';
const EditBook = () => {
const { id } = useParams();
const history = useHistory();
const [title, setTitle] = useState('');
const [author, setAuthor] = useState('');
const [year, setYear] = useState('');
useEffect(() => {
fetchBook();
}, []);
const fetchBook = async () => {
const response = await BookService.getBook(id);
const book = response.data;
setTitle(book.title);
setAuthor(book.author);
setYear(book.year);
};
const handleSubmit = async (e) => {
e.preventDefault();
await BookService.updateBook(id, { title, author, year });
history.push('/');
};
return (
<div>
<h2>Edit Book</h2>
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Title"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<input
type="text"
placeholder="Author"
value={author}
onChange={(e) => setAuthor(e.target.value)}
/>
<input
type="text"
placeholder="Year"
value={year}
onChange={(e) => setYear(e.target.value)}
/>
<button type="submit">Update</button>
</form>
</div>
);
};
export default EditBook;
Handle CRUD Operations:
Implement functionality for adding, updating, and deleting entities. Use POST, PUT, and DELETE requests to communicate with the backend.
import axios from 'axios';
const baseURL = 'http://localhost:8080/api/books';
const BookService = {
getAllBooks: () => axios.get(baseURL),
getBook: (id) => axios.get(`${baseURL}/${id}`),
addBook: (book) => axios.post(baseURL, book),
updateBook: (id, book) => axios.put(`${baseURL}/${id}`, book),
deleteBook: (id) => axios.delete(`${baseURL}/${id}`)
};
export default BookService;
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
Run the Application:
Navigate to your React app directory and run:
npm start
Your React app should start on port 3000 (by default) and display your CRUD application.
{
"name": "my-crud-app",
"version": "0.1.0",
"private": true,
"dependencies": {
"axios": "^0.21.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "^5.2.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig":
Step 3: Test Integration
Test Integration:
Test your full-stack CRUD application by adding, updating, and deleting entities through the frontend UI. Ensure that data is persisted correctly in the backend database.
1. Create a Book:
curl -X POST -H "Content-Type: application/json" -d '{"title":"Example Book","author":"John Doe","year":2022}' http://localhost:8080/api/books
2. Get All Books:
curl http://localhost:8080/api/books
3. Get a Specific Book by ID:
curl http://localhost:8080/api/books/{book_id}
Replace {book_id}
with the actual ID of the book you want to retrieve.
4. Update a Book:
curl -X PUT -H "Content-Type: application/json" -d '{"title":"Updated Book Title","author":"Jane Doe","year":2023}' http://localhost:8080/api/books/{book_id}
Replace {book_id}
with the actual ID of the book you want to update.
5. Delete a Book:
curl -X DELETE http://localhost:8080/api/books/{book_id}
Replace {book_id}
with the actual ID of the book you want to delete.
These curl
commands allow you to perform end-to-end testing by interacting with the RESTful endpoints of your Spring Boot application. You can use them to verify the functionality of your CRUD operations.
Conclusion:
Congratulations on completing the “Book Management System” tutorial! You’ve learned to build a full-stack CRUD application using Spring Boot and React. Now you can create, read, update, and delete books with ease. Keep coding and exploring new possibilities in your projects!