React and Java come together seamlessly in this three-part introduction to full-stack development with React and Spring Boot. Part 1 gets you started with a basic application framework that you can customize as needed. Credit: Cozine / Shutterstock One of the most popular stacks today is combining Spring Java on the back end with a React front end. Implementing a full-stack Spring Java React application requires many decisions along the way. This article helps you get started by laying out a project structure for both ends of the stack, then developing an app that supports basic CRUD operations. My next two articles will build on the foundation we establish here by incorporating a datastore and deploying the application into a production environment. Getting started with Spring Java and React There are very good reasons for Java’s long-lived popularity as a server-side platform. It combines unbeatable maturity and breadth with a long and ongoing history of innovation. Using Spring adds a whole universe of capability to your back end. React’s ubiquity and fully-realized ecosystem make it an easy choice for building the front end. To give you a taste of this stack, we’ll build a Todo app that leverages each of these technologies. The example might be familiar if you read my recent intro to HTMX for Java with Spring Boot and Thymeleaf. Here’s a peek at the Todo app’s user interface: Matthew Tyson For now, we’ll just save the to-do items to memory on the server. Setting up Spring and React There are several ways to go about setting up React and Spring together. Often, the most useful approach is to have two separate, full-fledged projects, each with its own build pipeline. We’ll do that here. If you’d rather focus on the Java build and make the React build secondary, consider using JHipster. Setting up two discrete builds makes it easier for different people or teams to work on just one aspect of the application. To start, we’ll create a new Spring Boot project from the command line: $ spring init iw-react-spring --dependencies=web --build=maven That command lays out a basic project with support for web APIs. As you see, we’re using Maven for the build tool. Now, we can move into the new directory: $ cd iw-react-spring Before doing anything else, let’s add the React project. We can create it by calling create react app from the react-spring directory: /iw-react-spring/src/main$ npx create-react-app app Now we have a src/main/app directory containing our React app. The features of this setup are that we can commit the entire app, both sides, to a single repository, then run them separately during development. If you try them, you’ll see that both apps will run. You can start the Spring app with: /iw-react-spring$ mvn spring-boot:run To start the React app, enter: /iw-react-spring/app$ npm start Spring will be listening on localhost:8080 while React listens on localhost:3030. Spring won’t do anything yet, and React will give you a generic welcome page. Here’s the project outline so far: /iw-react-spring /app – contains the React app /app/src – contains the react sources /src – contain the Spring sources The Spring Java back end The first thing we need is a model class for the back end. We’ll add it to src/main/java/com/example/iwreactspring/model/TodoItem.java: package com.example.iwjavaspringhtmx.model; public class TodoItem { private boolean completed; private String description; private Integer id; public TodoItem(Integer id, String description) { this.description = description; this.completed = false; this.id = id; } public void setCompleted(boolean completed) { this.completed = completed; } public boolean isCompleted() { return completed; } public String getDescription() { return description; } public Integer getId(){ return id; } public void setId(Integer id){ this.id = id; } @Override public String toString() { return id + " " + (completed ? "[COMPLETED] " : "[ ] ") + description; } } This is a simple POJO that holds the data for a todo. We’ll use it to shuttle around the to-do items as we handle the four API endpoints we need to list, add, update, and delete to-dos. We’ll handle those endpoints in our controller at src/main/java/com/example/iwreactspring/controller/MyController.java: package com.example.iwreactspring.controller; private static List<TodoItem> items = new ArrayList<>(); static { items.add(new TodoItem(0, "Watch the sunrise")); items.add(new TodoItem(1, "Read Venkatesananda's Supreme Yoga")); items.add(new TodoItem(2, "Watch the mind")); } @RestController public class MyController { private static List<TodoItem> items = new ArrayList<>(); static { items.add(new TodoItem(0, "Watch the sunrise")); items.add(new TodoItem(1, "Read Swami Venkatesananda's Supreme Yoga")); items.add(new TodoItem(2, "Watch the mind")); } @GetMapping("/todos") public ResponseEntity<List<TodoItem>> getTodos() { return new ResponseEntity<>(items, HttpStatus.OK); } // Create a new TODO item @PostMapping("/todos") public ResponseEntity<TodoItem> createTodo(@RequestBody TodoItem newTodo) { // Generate a unique ID (simple approach for this example) Integer nextId = items.stream().mapToInt(TodoItem::getId).max().orElse(0) + 1; newTodo.setId(nextId); items.add(newTodo); return new ResponseEntity(newTodo, HttpStatus.CREATED); } // Update (toggle completion) a TODO item @PutMapping("/todos/{id}") public ResponseEntity<TodoItem> updateTodoCompleted(@PathVariable Integer id) { System.out.println("BEGIN update: " + id); Optional<TodoItem> optionalTodo = items.stream().filter(item -> item.getId().equals(id)).findFirst(); if (optionalTodo.isPresent()) { optionalTodo.get().setCompleted(!optionalTodo.get().isCompleted()); return new ResponseEntity(optionalTodo.get(), HttpStatus.OK); } else { return new ResponseEntity(HttpStatus.NOT_FOUND); } } // Delete a TODO item @DeleteMapping("/todos/{id}") public ResponseEntity<Void> deleteTodo(@PathVariable Integer id) { System.out.println("BEGIN delete: " + id); Optional<TodoItem> optionalTodo = items.stream().filter(item -> item.getId().equals(id)).findFirst(); System.out.println(optionalTodo); if (optionalTodo.isPresent()) { items.removeIf(item -> item.getId().equals(optionalTodo.get().getId())); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } else { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } } } The ArrayList class and HTTP methods In addition to our endpoints, we have an ArrayList (items) to hold the to-dos in memory, and we pre-populate it with a few items using a static block. We annotate the class itself with Spring’s @RestController. This is a concise way to say to Spring Web: handle the HTTP methods on this class and let me return values from the methods as responses. Each method is decorated with an endpoint annotation, like @DeleteMapping(“/todos/{id}”), which says: this method handles HTTP DELETE requests at the /todos/{id} path, where {id} will be whatever value is on the request path at the {id} position. The {id} variable is obtained in the method by using the (@PathVariable Integer id) annotated method argument. This is an easy way to tie parameters on the path to variables in your method code. The logic in each endpoint method is simple, and just operates against the items array list. Endpoints use the ResponseEntity class to model the response, which lets you display the response body (if required) and an HTTP status. @RestController assumes application/json as the response type, which is what we want for our React front end. The React front end Now that we have a working back end, let’s focus on the UI. Move into the /iw-react-spring/src/main/app directory and we’ll work on the App.js file, which is the only front-end code we need (except for a bit of typical CSS in App.css>). Let’s take the /iw-react-spring/src/main/app/App.js file in two parts: the code and the template markup, beginning with the markup: <div className="App"> <header className="App-header"><h1>My TODO App</h1></header> <input id="todo-input" type="text" placeholder="Add a TODO" /> <button onClick={(e) => addTodo(document.getElementById('todo-input').value)}>Add TODO</button> <ul> {todos.map(todo => ( <li key={todo.id}> <input type="checkbox" checked={todo.completed} onChange={() => toggleTodoComplete(todo.id)} /> {todo.description} <button onClick={() => deleteTodo(todo.id)}>🗑</button> </li> ))} </ul> </div> See my GitHub repo for the complete file. Here, the main components are an input box with the ID todo-input, a button to submit it using the addTodo() function, and an unordered list element that is populated by looping over the todos variables. Each todo gets a checkbox connected to the toggleTodoComplete function, the todo.description field, and a button for deletion that calls deleteTodo(). Here are the functions for handling these UI elements: import './App.css'; import React, { useState, useEffect } from 'react'; function App() { const [todos, setTodos] = useState([]); // Fetch todos on component mount useEffect(() => { fetch('http://localhost:8080/todos') .then(response => response.json()) .then(data => setTodos(data)) .catch(error => console.error(error)); }, []); // Function to add a new TODO item const addTodo = (description) => { fetch('http://localhost:8080/todos', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ description }), }) .then(response => response.json()) .then(newTodo => setTodos([...todos, newTodo])) .catch(error => console.error(error)); }; // Toggle completion const toggleTodoComplete = (id) => { const updatedTodos = todos.map(todo => { if (todo.id === id) { return { ...todo, completed: !todo.completed }; } return todo; }); setTodos(updatedTodos); // Update completion fetch(`http://localhost:8080/todos/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ completed: !todos.find(todo => todo.id === id).completed }), }) .catch(error => console.error(error)); }; const deleteTodo = (id) => { const filteredTodos = todos.filter(todo => todo.id !== id); setTodos(filteredTodos); fetch(`http://localhost:8080/todos/${id}`, { method: 'DELETE' }) .catch(error => console.error(error)); }; We have functions for creation, toggling completion, and deletion. To load the to-dos initially, we use the useEffect effect to call the server for the initial set of to-dos when React first loads the UI. (See my introduction to React hooks to learn more about useEffect.) useState lets us define the todos variable. The Fetch API makes it pretty clean to define the back-end calls and their handlers with then and catch. The spread operator also helps to keep things concise. Here’s how we set the new todos list: newTodo => setTodos([...todos, newTodo]), In essence, we’re saying: load all the existing todos, plus newTodo. Conclusion Java and Spring combined with React provides a powerful setup, which can handle anything you throw at it. So far, our Todo example application has all the essential components for joining the front end to the back end. This gives you a solid foundation to use for applications of any size. Keep an eye out for the next two articles, where we will add a datastore and deploy the application to production. Jump to Part 2: Extending the application for persistence with MongoDB and Spring Data. Related content feature 14 great preprocessors for developers who love to code Sometimes it seems like the rules of programming are designed to make coding a chore. Here are 14 ways preprocessors can help make software development fun again. By Peter Wayner Nov 18, 2024 10 mins Development Tools Software Development feature Designing the APIs that accidentally power businesses Well-designed APIs, even those often-neglected internal APIs, make developers more productive and businesses more agile. By Jean Yang Nov 18, 2024 6 mins APIs Software Development news Spin 3.0 supports polyglot development using Wasm components Fermyon’s open source framework for building server-side WebAssembly apps allows developers to compose apps from components created with different languages. By Paul Krill Nov 18, 2024 2 mins Microservices Serverless Computing Development Libraries and Frameworks news Go language evolving for future hardware, AI workloads The Go team is working to adapt Go to large multicore systems, the latest hardware instructions, and the needs of developers of large-scale AI systems. By Paul Krill Nov 15, 2024 3 mins Google Go Generative AI Programming Languages Resources Videos