Pollito's Opinion on Spring Boot Development 10: Next.js?
Posted on November 26, 2024 • 5 minutes • 964 words • Other languages: Español
- Some context
- I do know some frontend
- How did I approach this challenge?
- Design by Contract principles in a Next.js application
- Deployment
- That’s all
Some context
This is the tenth part of the Spring Boot Development blog series.
- The objective of the series is to be a demonstration of how to consume and create an API following Design by Contract principles .
- To achieve that, we created a Java Spring Boot Microservice that handles information about users.
- You can find the code at this GitHub repo
- In this blog we are going to create a Next.js app that consumes the Java Spring Boot Microservice.
- You can find the code at this other GitHub repo
Let’s start!
I do know some frontend
I am not a frontend developer, I do not engage in frontend developer activities in my day to day.
Having said that, I do know some frontend:
- In 2021, when I was short on money, I wanted to get a real developer job, so I dived into tutorial hell.
- I had my first approach to React, with the infamous 5 hours freeCodeCamp tutorial . That was my canonical event, it pushed me into the React rabbit hole.
- I failed interviews at MercadoLibre , ensolvers , and CAT Technologies . That made me realize I didn’t know sh*t after the tutorials, which was totally true. Please use tutorials, but escape tutorial hell.
- During my time at RunaID
- In the SIGEM
project there was a time when I was the only developer left, and I had no chance but to deal with the frontend shenanigans.
- The frontend consisted on some old Bootstrap admin template, Javascript and Jquery, all glued together in .gsp files .
- I was given training in React.
- Got this certificate
- Did a little admin application for DOSEP using Next.js, I think 12? I don’t remember.
- In the SIGEM
project there was a time when I was the only developer left, and I had no chance but to deal with the frontend shenanigans.
But my biggest teachers in frontend (specially React) are: Theo , Josh , and Midudev .
How did I approach this challenge?
Because my taste and knowledge on UI/UX is not that great, I choose pre-made solutions out there.
- Followed schadcn/ui Next.js: Install and configure Next.js guide
- Used v0 for creating components.
- Used TanStack Table for the Users’ table.
- Followed Dave Gray’s Next.js Modal with Parallel and Intercepting Routes, shadcn/ui Dialog video for creating the User’s detail modal and page.
But the most important library I used was Orval RESTful client generator
Design by Contract principles in a Next.js application
In Orval Overview page you can find the following:
Orval is able to generate client with appropriate type-signatures (TypeScript) from any valid OpenAPI v3 or Swagger v2 specification
And in the OpenAPI Generator Overview , the tool we used in our Java Spring Boot microservice to create an API following Design by Contract principles , you can find the following:
OpenAPI Generator allows generation of API client libraries (SDK generation), server stubs, documentation and configuration automatically given an OpenAPI Spec (both 2.0 and 3.0 are supported)
It seems that is possible to implement Design by Contract principles in a Next.js app. Let’s check how it was made.
1. Orval configuration
In orval.config.ts :
- Point to the OpenAPI specification that the Next.js app will consume.
- Point to the folder were the generated apis and models will be saved.
- Recommended: add said folder to the .gitignore.
import { defineConfig } from "orval";
export default defineConfig({
users: {
output: {
mode: "split",
target: "src/__generated__/api/users/usersApi.ts",
schemas: "src/__generated__/api/users/model",
client: "react-query",
},
input: {
target: "src/openapi/userManagerBackend.yaml",
},
},
});
2. prebuild script in package.json
"prebuild": "npm run orval-build",
3. Use example
In the OpenAPI Specification
, we have an operation named findById
.
/users/{id}:
get:
tags:
- User
summary: Get user by identifier
operationId: findById
parameters:
- description: User identifier
in: path
name: id
required: true
schema:
format: int64
type: integer
responses:
'200':
description: A user
content:
application/json:
schema:
$ref: '#/components/schemas/User'
default:
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
On pre-build, Orval generated a function useFindById
. Here’s an example on how to use it:
src/app/users/[id]/page.tsx
"use client";
import { useParams } from "next/navigation";
import { getId } from "./_utils/params-utils";
import Loading from "@/components/v0/loading";
import AxiosErrorAlert from "@/components/v0/axios-error-alert";
import UserCard from "./_components/user-card";
import RedirectButton from "@/components/v0/redirect-button";
import { useFindById } from "@/__generated__/api/users/usersApi";
const UserDetails = () => {
const id = getId(useParams<{ id: string }>());
const {
isPending,
isError,
data: response,
error,
} = useFindById(id, {
axios: { baseURL: process.env.NEXT_PUBLIC_API_USERS_BASE_URL },
});
if (isPending) {
return <Loading />;
}
if (isError) {
return <AxiosErrorAlert axiosError={error} />;
}
return (
<div className="flex flex-col items-center pt-4">
<div className="flex flex-col items-center gap-4 w-full max-w-md">
<UserCard user={response.data} />
<div className="w-full flex justify-end">
<RedirectButton href="/users" label="See Users" />
</div>
</div>
</div>
);
};
export default UserDetails;
Deployment
Don’t overcomplicate, and just deploy in a Vercel free plan. Don’t forget to add the ENV secrets to point to the backend, and the pagination size (personal preference to have it as a global thing).
.env.example
NEXT_PUBLIC_API_USERS_BASE_URL=https://user-manager-backend-den3.onrender.com
NEXT_PUBLIC_API_USERS_PAGE_SIZE=5
That’s all
It was a long journey, that started all the way back in December 2023 with old blogs (now deleted), treating the same concepts I did in this series, Design by Contract Principles.
There are some minor rough edges to fix here and there:
- In the frontend app, the pagination for some reason inserts twice in the browser history.
- In the backend app:
- the H2 version could benefit in using Eager Loading.
- the feignClient version maps every element before even filtering them. On 10 elements is not big deal, but it is not the correct approach.
Nonetheless, I’m happy with the result as it is right now.
I invite you to check other stuff in my blog, as my passion for web development will keep me writing blogs for sure.
Cheers! Pollito <🐤/>.