In this post, you will see how to build a full stack application using Spring boot API with ReactJS frontend. In the last few years, microservices have been all the rage and people are increasingly turning towards using them. The way people talk about the benefits of Microservices can at times make it seem that monolithic web apps are dated and there isn’t a use case for them anymore. That could not be further from the truth, monolithic web apps still have a very valid use case, one of which would be to build a quick prototype and validate a proof of concept. This post aims to show you with code samples on how to build a Spring Boot API that communicates with a reactjs based UI. Code samples include the code for Maven plugins that can build the react app and start it when running the API using maven commands. All the source code used in this post is stored on Github.
Web app
You would create a simple web app that manages a certain resource e.g. Users Bank account. You will be building something that exposes an API to CRUD account information. Once the API is built and tested via Postman, you will build a ReactJS based UI the allows doing all that from a browser. Please be mindful of the fact that, the objective here is for you to learn how to achieve our objective, hence, there will be no fancy UI or extensive unit test coverage for Java spring boot API. Without further a due, let’s get started.
Spring boot API with ReactJS frontend: App structure
asd
Spring boot API
Let’s take a look at some of our model classes first,
Models
Account.java
package com.mydaytodo.template.springbootreact.model;
import lombok.Builder;
import lombok.Data;
import lombok.ToString;
import lombok.extern.java.Log;
import java.math.BigDecimal;
@Data
@ToString
@Log
@Builder
public class Account {
public static String ID_PATTERN = "ACT_";
private String accountId;
private String name;
private BigDecimal balance;
private String accountType; // mapped from the Account type enum
public void update(Account updateWith) {
if(updateWith.getName() != null)
this.setName(updateWith.getName());
if(updateWith.getBalance() != null)
this.setBalance(updateWith.getBalance());
if(updateWith.getAccountType() != null)
this.setAccountType(updateWith.getAccountType());
}
}
ApiResponse.java (you will see how this class is used in the controller later)
package com.mydaytodo.template.springbootreact.model;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.java.Log;
@Data
@ToString
@Log
@Builder
public class ApiResponse {
private int responseCode;
private Object data;
private String message; // to be populated in case of an error
}
AccountType.java
package com.mydaytodo.template.springbootreact.model;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@ToString
public enum AccountType {
CHEQUE("cheque"),
SAVINGS("savings"),
CREDIT("credit");
private String accountType;
AccountType(String accountType) {
this.accountType = accountType;
}
}
Controller, Services and DAO
AccountsController.java
package com.mydaytodo.template.springbootreact;
import com.mydaytodo.template.springbootreact.model.Account;
import com.mydaytodo.template.springbootreact.model.ApiResponse;
import com.mydaytodo.template.springbootreact.service.AccountServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping("/api/account")
@Slf4j
public class AccountController {
@Autowired
private AccountServiceImpl accountService;
@PostMapping("/")
public ResponseEntity<ApiResponse> createAccount(@RequestBody Account account) {
log.info(String.format("Received request to add acount -> [ %s ]", account.toString()));
// do something
ApiResponse response = accountService.addAccount(account);
return new ResponseEntity<>(response, HttpStatus.valueOf(response.getResponseCode()));
}
@GetMapping("/{account-id}")
public ResponseEntity<ApiResponse> getAccount(@PathVariable("account-id") String accountId) {
ApiResponse validation = accountValidator.accountExists(accountId);
if(validation != null) {
return new ResponseEntity<>(validation, HttpStatus.valueOf(validation.getResponseCode()));
}
ApiResponse response = accountService.getAccount(accountId);
return new ResponseEntity<>(response, HttpStatus.valueOf(response.getResponseCode()));
}
@PutMapping("/update/{account-id}")
public ResponseEntity<ApiResponse> updateAccount(@PathVariable("account-id") String accountId, @RequestBody Account account) {
ApiResponse validation = accountValidator.accountExists(accountId);
if(validation != null) {
return new ResponseEntity<>(validation, HttpStatus.valueOf(validation.getResponseCode()));
}
ApiResponse response = accountService.updateAccount(accountId, account);
return new ResponseEntity<>(response, HttpStatus.valueOf(response.getResponseCode()));
}
@DeleteMapping("/{account-id}")
public ResponseEntity<ApiResponse> deleteAccount(@PathVariable("account-id") String accountId) {
ApiResponse validation = accountValidator.accountExists(accountId);
if(validation != null) {
return new ResponseEntity<>(validation, HttpStatus.valueOf(validation.getResponseCode()));
}
ApiResponse response = accountService.deleteAccount(accountId);
return new ResponseEntity<>(HttpStatus.valueOf(response.getResponseCode()));
}
@GetMapping(value = "/all", produces = "application/json")
public ResponseEntity<ApiResponse> getAllAccounts() {
log.info("Received request to get all accounts");
ApiResponse response = accountService.getAllAccounts();
return new ResponseEntity<>(response, HttpStatus.valueOf(response.getResponseCode()));
}
}
AccountServiceImpl.java
package com.mydaytodo.template.springbootreact.service;
import com.mydaytodo.template.springbootreact.dao.AccountDaoImpl;
import com.mydaytodo.template.springbootreact.model.Account;
import com.mydaytodo.template.springbootreact.model.ApiResponse;
import lombok.extern.java.Log;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
/**
* Ideally, this should implement an interface for AcountService
* However, ignoring it here to keep the code brief here
*/
@Service
@Log
public class AccountServiceImpl {
@Autowired
private AccountDaoImpl accountDao;
public ApiResponse addAccount(Account account) {
log.info("About to generate id");
String id = Account.ID_PATTERN + System.currentTimeMillis();
account.setAccountId(id);
log.info("Adding value "+ id);
return accountDao.addNewAccount(account);
}
public ApiResponse deleteAccount(String accountId) {
return accountDao.deleteAccount(accountId);
}
public ApiResponse updateAccount(String accountId, Account account) {
return accountDao.updateAccount(accountId, account);
}
public ApiResponse getAccount(String accountId) {
return accountDao.getAccountFor(accountId);
}
public ApiResponse getAllAccounts() {
return accountDao.getAllAccounts();
}
}
AccountDaoImpl.java
package com.mydaytodo.template.springbootreact.dao;
import com.mydaytodo.template.springbootreact.model.Account;
import com.mydaytodo.template.springbootreact.model.ApiResponse;
import lombok.extern.java.Log;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Component
@Log
public class AccountDaoImpl {
private List<Account> accounts = new ArrayList();
public List<Account> getAccounts() {
return accounts;
}
public Optional<Account> getAccount(String accountId) {
return accounts.stream()
.filter(account -> account.getAccountId().equals(accountId))
.findFirst();
}
public ApiResponse addNewAccount(Account account) {
log.info(String.format("About to add account [ %s ]", account.toString()));
accounts.add(account);
return ApiResponse.builder()
.data(account)
.responseCode(HttpStatus.CREATED.value())
.message("")
.build();
}
public ApiResponse getAccountFor(String accountId) {
Optional<Account> account = getAccount(accountId);
return ApiResponse.builder()
.data(account.get())
.responseCode(HttpStatus.OK.value())
.build();
}
public ApiResponse deleteAccount(String accountId) {
accounts = accounts.stream()
.filter(account -> !(account.getAccountId().equals(accountId)))
.toList();
return ApiResponse.builder()
.responseCode(HttpStatus.NO_CONTENT.value())
.data(accounts)
.build();
}
public ApiResponse updateAccount(String accountId, Account account) {
accounts.stream()
.filter(account1 -> account1.getAccountId().equals(accountId))
.findFirst()
.ifPresent(account1 -> account1.update(account));
return ApiResponse.builder()
.responseCode(HttpStatus.NO_CONTENT.value())
.message("")
.data(accounts)
.build();
}
public ApiResponse getAllAccounts() {
log.info("Received request to get all accounts");
return ApiResponse.builder()
.responseCode(HttpStatus.OK.value())
.message("")
.data(accounts)
.build();
}
}
To run this API, simply type mvn spring-boot:run
React frontend
As mentioned before, the code for the react app is kept to a bare minimum and I have not added fancy CSS to make it all work.
import logo from './logo.svg';
import { useEffect, useState } from "react";
import './App.css';
function App() {
const [allAccounts, setAllAccounts] = useState([]);
const [balance, setBalance] = useState(0.0);
function createNewAccount(e) {
e.preventDefault();
const accountName = document.getElementsByName("name")[0].value;
const accountType = document.getElementsByName("accountType")[0].value;
const balance = document.getElementsByName("balance")[0].value;
const newAccountSignup = {
"name": accountName,
"balance": balance,
"accountType": accountType
};
fetch("/api/account/", {
method: "POST",
"headers": {
"Content-Type": "application/json",
},
body: JSON.stringify(newAccountSignup)
}).then((resp) => {
console.log(resp);
})
}
async function getAllAccounts() {
const urlStr = "/api/account/all";
const resp = await fetch(urlStr);
const responseJson = await resp.json();
console.log(responseJson);
setAllAccounts(responseJson.data);
}
async function deleteAccount(id) {
console.log(`Received query to delete -> ${id}`);
const urlStr = `/api/account/${id}`;
fetch(urlStr, {
method: "DELETE",
}).then((resp) => {
console.log("Deleting successfull");
console.log(resp);
getAllAccounts();
})
}
return (
<div className="App">
<h1> Welcome to Spring Boot react template app</h1>
<p> where you to create and manage your bank accounts </p>
<div id="addAccountForm" style={{ display: 'flex', flexDirection: 'column' }}>
<form onSubmit={createNewAccount}>
<h3> Add account </h3>
<div id="nameFields">
<p>
Name: <input type='text' name='name' />
</p>
</div>
<div id="balanceFields">
<p>
Balance: <input type='number' name='balance' />
</p>
</div>
<div id="accountType">
<p>
Type: <input type='text' name='accountType' />
</p>
</div>
<div>
<button className="btn btn-sm btn-success" type="submit">
{" "}
Add{" "}
</button>
</div>
</form>
</div>
<div id="listAccounts">
<h4> Update and delete functionality to be added here</h4>
<button onClick={getAllAccounts}> Fetch Accounts</button>
{allAccounts.map((account) => (
<div key={account.accountId} style={{display: 'flex', flexDirection:"row", justifyContent: "center", padding: 8}}>
<div style={{padding: 3}}>Name: <b>{account.name}</b> </div>
<div style={{padding: 3}}>Balance: <b>{account.balance}</b> </div>
<div style={{padding: 3}}>Account Type: <b>{account.accountType} </b></div>
<div style={{padding: 3}}><button onClick={() => deleteAccount(account.accountId)}> delete</button></div>
</div>
))}
</div>
<div id="accountDetails">
</div>
</div>
);
}
export default App;
How to start react app when starting the API with Maven?
This is done via the pom.xml and have a look at the code for the frontend-maven-plugin
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.mydaytodo.template</groupId>
<artifactId>spring-boot-react</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-react</name>
<description>Template for a Spring Boot API with React front-end</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<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>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>1.6</version>
<configuration>
<workingDirectory>frontend</workingDirectory>
<installDirectory>target</installDirectory>
</configuration>
<executions>
<execution>
<id>install node and npm</id>
<goals>
<goal>install-node-and-npm</goal>
</goals>
<configuration>
<nodeVersion>v14.21.3</nodeVersion>
<npmVersion>6.14.18</npmVersion>
</configuration>
</execution>
<execution>
<id>npm install</id>
<goals>
<goal>npm</goal>
</goals>
<configuration>
<arguments>install</arguments>
</configuration>
</execution>
<execution>
<id>npm run build</id>
<goals>
<goal>npm</goal>
</goals>
<configuration>
<arguments>run build</arguments>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<phase>generate-resources</phase>
<configuration>
<target>
<copy todir="${project.build.directory}/classes/public">
<fileset dir="${project.basedir}/frontend/build"/>
</copy>
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Conclusion
Hope you found this blogpost useful and you can find the full source code for this repo here on Github which also has instructions to setup and run this repo.
If you find any of my posts useful and want to support me, you can buy me a coffee 🙂
https://www.buymeacoffee.com/bhumansoni
While you are here, maybe try one of my apps for the iPhone.
Products – My Day To-Do (mydaytodo.com)
Have a read of some of my other posts on AWS
Deploy NodeJS, Typescript app on AWS Elastic beanstalk – (mydaytodo.com)
How to deploy spring boot app to AWS & serve via https – My Day To-Do (mydaytodo.com)
Some of my other posts on Javascript …
What is Javascript event loop? – My Day To-Do (mydaytodo.com)
How to build a game using Vanilla Javascript – My Day To-Do (mydaytodo.com)
Vanilla Javascript: Create Radio Buttons (How-To) – Bhuman Soni (mydaytodo.com)
Java Spring Boot & Vanilla Javascript solution – My Day To-Do (mydaytodo.com)
Vanilla Javascript: Create Radio Buttons (How-To) – Bhuman Soni (mydaytodo.com)
7 Comments
Upload to AWS S3 bucket from Java Spring Boot app - My Day To-Do · March 13, 2024 at 4:55 am
[…] How to build a full stack Spring boot API with ReactJS frontend – My Day To-Do (mydaytodo.com) […]
File share app - social file share feature - My Day To-Do · March 17, 2024 at 4:05 pm
[…] How to build a full stack Spring boot API with ReactJS frontend – My Day To-Do (mydaytodo.com) […]
How to add basic auth to Spring Boot API - My Day To-Do · April 4, 2024 at 3:34 am
[…] How to build a full stack Spring boot API with ReactJS frontend – My Day To-Do (mydaytodo.com) […]
How to fix 403 error in Github actions CI/CD - My Day To-Do · June 23, 2024 at 10:58 pm
[…] How to build a full stack Spring boot API with ReactJS frontend – My Day To-Do (mydaytodo.com) […]
How to catch ExpiredJwtException in Spring OncePerRequestFilter - My Day To-Do · July 25, 2024 at 1:25 pm
[…] How to build a full stack Spring boot API with ReactJS frontend – My Day To-Do (mydaytodo.com) […]
How to build a blog engine with React & Spring Boot - Part 1 - My Day To-Do · September 21, 2024 at 12:35 am
[…] How to build a full stack Spring boot API with ReactJS frontend – My Day To-Do (mydaytodo.com) […]
How to build a blog engine with React & Spring Boot – Part 3 CI/CD pipeline · October 5, 2024 at 12:52 pm
[…] How to build a full stack Spring boot API with ReactJS frontend – My Day To-Do (mydaytodo.com) […]