

Discover more from INUVEON
Reactive Spring Boot API with Next.js Client
Let´s extend the Reactive API Service with a cool reactive UI

In this article, I want to extend the Course Service API from my previous article with a user interface. I will also continue to follow the path of reactive components.
Let me briefly summarize what we did in the last post. We have created a microservice with Spring Boot that allows us to create new courses. We have added two more GET endpoints to this service. A conventional one, which allows retrieving all courses. The second endpoint can be subscribed to by clients and informs them about new courses. We have used Spring Webflux for this.
Now I want to extend the service with user interface components.
In 2015 I started to use Reactjs to implement cool user interfaces. I was immediately fascinated by how well front-end code can be structured. I also liked the fact that you could concentrate on the “view” without boilerplate. And last but not least the fact that you could simply update single page elements without having to reload the whole page. Sure, there were similar concepts before that made this possible. But I always found it quite cumbersome. Since then I used Reactjs whenever it was convenient or possible. In all past projects, I rendered React on the client side. For me, there was usually no big problem, because it worked great and mostly public APIs were used.
Most of the systems were designed as shown in the picture above. The business logic was split up into small individual services, which were rolled out on a Kubernetes cluster. The individual services were exposed to the outside world via an NGINX Ingress Controller. The user interface was implemented as a standalone application and rolled out on a dedicated web server. The API Gateway allows the React application to access the published REST endpoints. From my point of view this is a very common way.
But what if I want to give each service its own UI component and roll it out on the Kubernetes cluster?
I want to deploy the REST API and the client component in one Kubernetes pod per microservice as shown in the picture above. In this case, for example, we can ensure that there are no version conflicts between API and UI because we can roll out both containers with the same release pipeline.
But what I have to avoid in this case is that the UI component is rendered on the client side and therefore has to call back into the cluster via an API Gateway to get to the API endpoints of its service.
At this point server-side rendering (SSR) comes into play. Server-side rendering means that the JavaScript of a website is rendered on the server of the website.
I do not want to go into the pros and cons of server-side rendering in depth here in my article. However, one advantage is in any case the performance of webpages. A disadvantage can be that more load is needed on the server side for rendering. Anyway, my main concern is that we can deliver microservices with UI and for the UI components API calls are made over public endpoints.
I plan to use an NGINX as an Ingress Controller, which will route the requests from the Internet to the corresponding user interfaces of the individual microservices. The communication of the UI component with the REST API should be done within Kubernetes from container to container, or in terms of Kubernetes: the UI container calls the REST API container via the service resource layer. I will not discuss the operation of the service components on a Kubernetes cluster in this article. I will discuss this in more detail in one of my next articles.
First, we will add a UI client to our REST API project, which we created with Spring Boot/ Webflux in the last article. I want to use Next.js for this. If you haven’t heard of Next.js: It is a minimalist framework for server-rendered React applications. In my opinion Next.js deliver a ready-to-go platform. You can just install it. And you have everything what you need to start our project.
The reason for using Next.js is, as explained before, that I would like to use React.js on the one hand, but on the other hand I would like to render server-side. This is of course also possible without Next.js, but of course not without the corresponding effort.
Let’s finally integrate the UI components into Course microservice we created last time. Currently (resulting from the last article) we have the following project structure available, see the following figure.
Create and Prepare the Next.js Client
Let´s create the Client app on the same level as the /src folder. We can do this easily using the following command.
<a href="https://medium.com/media/4b0a8b109c8c049235186c479cd917ad/href">https://medium.com/media/4b0a8b109c8c049235186c479cd917ad/href</a>
Now we’ve got the following project structure.
<a href="https://medium.com/media/f36429901805b2b99b3d204688e5b3c6/href">https://medium.com/media/f36429901805b2b99b3d204688e5b3c6/href</a>
The good thing is that our Course Service Client App is already up and running.
We can test this with the command: $ cd client && yarn dev
After that we can simply call http://localhost:3000 in the browser to see the typical Next.js Index page.
We remember the scenario from the last article. The REST API creates the following endpoints ready.
<a href="https://medium.com/media/28bcdd0fb624426c51a0e750a2530e65/href">https://medium.com/media/28bcdd0fb624426c51a0e750a2530e65/href</a>
Now we will need these endpoints for the implementation of the user interface. Basically we need two pages: one page that lists all courses and another page that allows the creation of new courses.
Before we start we can delete a few things from the just created Next.js app. In the folder pages there is a folder api. The content of that folder can be deleted. In the folder styles we can also delete the file Home.modules.css, because we will not need the styles defined in it.
First, let us define some CSS. You can copy/paste the following styles into the already existing global.css in folder styles. This is nothing special, and we will not win the designer championship. But we need this later to display the table and the form for creating new courses.
<a href="https://medium.com/media/88a487b8c9fd20aeae792f54c7ab6940/href">https://medium.com/media/88a487b8c9fd20aeae792f54c7ab6940/href</a>
Next, I want to adjust the Index page so that we see a list of existing courses.
In my last article I described how we run our API with a H2 In-Memory DB. To have some data available initially I will now extend our Spring Boot Rest API with a data initializer class to create a few example data.
Adjust Spring Boot API
Therefore I rename the class CategoryInitializer to DataInitializer and extend it with the private method createExampleCourses as shown in the following code snippet.
private void createExampleCourses(List<Category> categories){
List<Course> courses = new ArrayList<>() {
{
add(new Course("Outdoor Bootcamp", categories.get(0), UUID.randomUUID(), 60L));
add(new Course("Hurricane Bootcamp", categories.get(0), UUID.randomUUID(), 45L));
add(new Course("Six Pack Workout", categories.get(3), UUID.randomUUID(), 45L));
add(new Course("XXL Legs Workout", categories.get(4), UUID.randomUUID(), 90L));
}
};
this.courseRepository.saveAll(courses);
log.debug("Sample courses created.");
}
This new method is called by onApplicationEvent method after all categories have been created.
@Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
...
this.createExampleCourses(categories);
}
If we now restart our Spring Boot Application, the four sample courses will be created. These will be displayed first in our new Next.js in the Index page as a list.
In the folder pages we find a file named Index.js. We now extend it with the function getInitialProps to fetch data from the REST API as shown below.
Index.getInitialProps = async () => {
const res = await fetch('http://localhost:4500/course');
const json = await res.json();
return {
courses: json
}
};
Next.js provides getStaticProps and getServerSideProp to get data. They are mainly used to fetch data from external sources. In doing so, getInitialProps can focus on preparing props that are populated before rendering the page.
Now let´s replace the existing home function with the following code. The list of courses will be fetched initially on the server side before rendering as described above.
const Index = ({ courses }) => {
const courseList = courses.map(course => {
return <tr key={course.id}>
<td style={{whiteSpace: 'nowrap'}}>{course.title}</td>
<td>{course.categoryTitle}</td>
<td>{course.duration}</td>
</tr>
});
return (
<div >
<Head>
<title>Course Service Example</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<div>
<h1>Courses</h1>
<table>
<thead>
<tr>
<th width="40%">Title</th>
<th width="30%">Category</th>
<th width="30%">Duration (minutes)</th>
</tr>
</thead>
<tbody>
{courseList}
</tbody>
</table>
</div>
</div>
)
}
... // getInitialProps
export default Index;
If we now call yarn dev, go then to http://localhost:3000 in the browser, we get the following page.
The following output was created by calling yarn build .The figure shows that the Index page is a server file. We recognize this by the lambda symbol λ to the left to the page name ‘/’.
But, watch out!!!
The build output says that the data is fetched on the server side. And this also happens when you call the page directly. You can see this very well in the console log of the browser, because there is no output in the console log when you call the page directly. This is due to the fact that the data is fetched from the server. Basically exactly what we want.
But there is one problem that made me desperate when I started to work with Next.js: If we navigate to this page from another location, for example the component <Link /> from the Next.js library, the data is fetched on the client side.
To prevent this we have to use getServerSideProps instead of getInitialProps, see the following code example. This is in our case much better than the classic hybrid approach of getInitialProps.
export const getServerSideProps = async () => {
console.log("Fetch data...");
const res = await fetch('http://localhost:4500/course');
const json = await res.json();
return { props: {
courses: json
}
}
};
The “Create Course” Page
Now let’s create a new page to add new courses via the UI. For this we create a new file named course.js in /pages.
In the following picture we see how the page should look like.
You can see that we have a select box there where the user can select the category of the course. For that we have to request the REST API, of course. And as mentioned before we have to do this server-side. So again we need a getServerSideProps function to retrieve the available categories, see the following code snippet.
export const getServerSideProps = async () => {
const res = await fetch(
`http://localhost:4500/course/category`);
const data = await res.json();
return { props: {
categories: data,
userId: uuid() // Fake user id
}
}
}
In this function we load the initial props, that is the list of categories and a random UserId. We need this ID later when we call the POST endpoint. Since we have not yet integrated authentication, we do not have the ID of the current user. To generate a random UUID I use the node module ‘uuid’.
Add this module:
$ yarn add uuid
As a Create Course component or page I now implement a React Hook and give the component the props created on the server side.
const NewCourse = ({ categories, userId }) => {
return <div>
<h1>Create New Course</h1>
<form onSubmit={handleSubmit}>
...
</div>
}
export default NewCourse;
As indicated in the code snippet, we will build a form that calls the function handleSubmit using the onSubmit handler.
This handleSubmit function becomes part of our React hook and is responsible for sending the data to the API.
const handleSubmit = async(e) => {
e.preventDefault();
await fetch(`http://localhost:4500/course`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(course),
}).then(res => {
if(res.status === 200){
setSaved(true);
}else{
setError('Error: ' + res.status + ' :: '
+ res.statusText);
}
});
}
But what happens here when we call fetch directly in the client code? It will work perfect as long as we run the services on our local dev environment where we can reach the localhost:4500. Under real conditions it would only be possible to address the API via a public endpoint.
But our goal is to perform the communication with the back-end on the server side. Next.js offers an excellent way to do this, namely the /api routes. Below the folder /pages is the folder /api. Everything we define within /pages/api is routed to /api/*. Basically it is a way to offer an own API. Everything in the /api folder is server-side bundled and has nothing to do with client side bundle.
This means that we need our REST API call in as /api route within the next.js app. Therefore we create a new folder /pages/api/course, and in the new folder a file index.js.
To define route handlers comfortably and clearly, I use the lightweight library next-connect.
$ yarn add next-connect
By adding the following code to the new index.js file in the /course folder, we already have a working API route with Next.js.
import nextConnect from 'next-connect';
const handler = nextConnect();
handler.post(async (req, res) => {
console.log("api call");
const course = req.body.course;
await fetch(`http://localhost:4500/course`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(course),
}).then(r => {
if(r.status === 201){
res.json({ message: "ok" });
}else{
res.status(r.status).json({status: r.status, message: r.statusText})
}
});
});
export default handler;
Basically we moved the REST API call into the server side bundle of our app. From the course page code (React Hook) we can now address our new API by simply addressing the POST call to /api/course, see the following code example.
const handleSubmit = async (e) => {
e.preventDefault();
await fetch('/api/course', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
course: course,
}),
}).then(res => {
if(res.status === 200){
setSaved(true);
}else{
setError('Error: ' + res.status + ' :: ' + res.statusText);
}
});
}
Cool, now we already have our site to create new courses, which executes all necessary requests to the Spring Boot back-end server side.
Reactive Course List
Now we come to the highlight of our little app. Of course we don’t want User A to have to reload his list of available courses when User B adds a new course.
For this we have to subscribe to the SSE stream of our Spring Boot REST API. We had already looked at this in the last article from the point of view of Terminal and Postman.
With everything we have done so far, we have gained enough knowledge to implement this part as well. We know that we are not allowed to make any calls against the REST API in the React Hook, because they would be executed client-side. In the “Create Course” part we showed that we can use the /api route of Next.js to create a server-side bundle.
We have already implemented a POST handler that can be called via the endpoint /api/course for the client code.
Now what do we have to do to subscribe to course updates (Server Sent Events) from the Spring Boot API?
We have to implement a SSE client. To do this, we first add the Dependency eventsource to our project.
$ yarn add eventsource
And import this module into the course API module (/api/course/index.js).
import EventSource from 'eventsource';
Now we are able to subscribe to server send events via node.js. We create the function stream.
const stream = async (req, res) => {
console.log("connect to sse stream");
let eventSource = new EventSource(`${API_HOST}/course/sse`);
eventSource.onopen = (e) => { console.log('listen to sse endpoint now', e)};
eventSource.onmessage = (e) => {
res.flushData(e.data);
};
eventSource.onerror = (e) => { console.log('error', e )};
// close connection (detach subscriber)
res.on('close', () => {
console.log("close connection...");
eventSource.close();
eventSource = null;
res.end();
});
}
In case an event occurs (onmessage), flushData is called and the received data is transferred. For this we have to implement a piece of middleware, which is basically just a small function, see next code snippet.
const sseMiddleware = (req, res, next) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.flushHeaders();
const flushData = (data) => {
const sseFormattedResponse = `data: ${data}\n\n`;
res.write("event: message\n");
res.write(sseFormattedResponse);
res.flush();
}
Object.assign(res, {
flushData
});
next();
}
Basically we set the required response headers and make sure that the response is formatted correctly, and written.
Now we can use this API endpoint from the client code and it is ensured that the connection to the Spring Boot back-end is server-side and no public API endpoints are required.
The following code shows how the course list page (/pages/index.js) can subscribe to the new node.js SSE Endpoint. Onmessage parses the received data from the stream to JSON and pushes the new data to the course list.
useEffect(() => {
let eventSource = new EventSource(`/api/course`);
eventSource.onopen = (e) => { console.log('listen to api-sse endpoint', e)};
eventSource.onmessage = (e) => {
const course = JSON.parse(e.data);
if (!courses.includes(course)){
setCourses( courses => [...courses, course]);
}
};
eventSource.onerror = (e) => { console.log('error', e )};
// returned function will be called on component unmount
return () => {
eventSource.close();
eventSource = null;
}
},[])
That’s it, so the list of courses is able to listen to new events and update the list.
The concept of Next.js seems to me to be very coherent and easy to use once you have understood how it works and where to place which functions. To be honest, I was sometimes a bit confused at the beginning.
As always, you can find the entire project on GitHub. Have fun and happy coding!
In one of my next articles I will show how to up and run the Spring Boot API services and the Next.js front-end app on a Kubernetes cluster.
I am always happy to receive feedback, suggestions, criticism or questions. If you liked the article or even helped with current problems, then please leave a Clap there.
Cheers!
Reactive Spring Boot API with Next.js Client was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.