Dockerize Spring Boot App with Frontend in React

Frontend for stoic application
Some time ago I created simple Spring Boot app with basic RESTful API and PostgreSQL backend (see this post in which I document my struggle with my first Docker app). Now is the time to create a simple frontend.
A very basic backend application stoic_cafe requires a simple GUI so that I can hit my RESTful endpoints through the browser. Let’s write a simple javascript client that will do just that:
- display a list of quotes and a list of thoughts
- allow edition and deletion
I initially decided to start with Preact which somehow seems to be a bit less scary than React.
Set up preact application
I started with a template application as shown in preact.js guide:
|
|
Then I started the app in development mode and started to play.
|
|
Components
I modified autogenerated Home component a bit. I wanted to display Quotes component (that would - when mounted - display a list of Quote components).
Quote
The Quote component’s code is really simple.
Here I assume that I would receive simple properties for display (quote, author) and delete
callback which I call when button is clicked.
|
|
Quotes
The callback is received as a property from parent component (Quotes) that manages all quotes as its state. Here is hoq Quotes is implemented:
|
|
Fetch api
In order to fetch data from server and also to send other types of requests (POST
new quotes or DELETE
them), I use fetch API.
CORS
In order to be able to access responses from server I needed to use CORS in a very simple way:
- all requests - from all origins - are allowed
- methods allowed are: GET, POST and DELETE
After a while I changed my assumptions a bit:
- only allow connections from localhost
- and only from two ports:
- server application port for pure REST
- and also connections from gui application (also runnig on localhost)
Configuration in Spring Boot
In Spring Boot this is very simple. I just need to configure one @Bean
which uses CorsRegistration instances for specific cors rules.
- I don’t have any separation of cors rules for different parts of my app, therefore the mapping I define is for all paths (
addMapping("/**")
) - I want my app to be accessible from the browser, therefore I allow access from
server.port
- I also want to access the app from the Preact client application, which I want to run on
gui.port
- All access is done from localhost, hence the
localhost
host is hardcoded
|
|
Note to self: have a look at Spring Cloud Config
React, not Preact
I wanted to experiment a bit with react component libraries and Preact with its compaibility layer was a bit inconvenient. So I used create-react-app.
React App issues
I had a lot of unknowns with my little app:
- in Dockerfile, how to pass environment variables to the application? I need to pass
server.port
(for RESTful API) andgui.port
(to allow the client in COSR) … - what happens if I
npm run build
instead ofnpm run
? - how to write ENTRYPOINT ?
I just tried to follow this great tutorial which came first when I searched for dockerize create-react-app
.
But I’ll be honest, I don’t know what I’m doing.
My Dockerfile for React app
|
|
My docker-compose
And finally my docker-compose:
|
|
As you can see, I use two environment variables, GUI_PORT
and SERVER_PORT
and pass them:
- to
app
service so that the app can correctly configure cors for connections from server and from client - to
gui
service - it actually only needsSERVER_PORT
to know what port to hit when accessing app
Random problems
During development of a simple gui client I stumbled upon several small “problems”. The solutions to those problems are certainly very well known for full stack devs, but for me they were just little obstacles that made my way to learn more. Here is what I have lerned:
SequenceGenerator
Postresql default sequence allocation size is 1 and Hibernate (or JPA) by default uses value 50. I’ve noticed that an exception is raised when Hibernate tries to create the genrator on its side (see problem and its allocation size differs from the allocation size I defined in database sequence.
The solution and a good practice is this:
- be explicit when declaring allocation size in @SequenceGenerator:
1 2 3 4
@Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = GENERATOR_NAME) @SequenceGenerator(name = GENERATOR_NAME, sequenceName = SEQUENCE_NAME, allocationSize = 50) private Long id;
- use the same value in sql code and in JPA code; here is what I needed to do:
1 2 3
create sequence hibernate_sequence start 1 increment 1; -- (... and in separate changeset...) alter sequence hibernate_sequence increment by 50;
Who stole my port
I sometimes forgot that I started my app in the background and I’m supprised to see that the port I want to start another one is busy. In order to identify the PID of the java process I can use a very useful tool - lsof
.
|
|
Stopping a spring-boot app
Once the PID is found, I can simply
|
|
which is a kind notification that the app should shut down (instead of kill -9 <PID>
which causes the OS to forcibly kill the app)
Of course, a more system-friendly way of sutting down the app, when it was started with
|
|
is to use:
|
|
This ensures proper closing of all resources, like connections to the database. When spring-boot app is run with
|
|
then it stays connected to the terminal and simple
See this article for a description of how to close spring-boot application.
Spring Boot testing
This is a very wide topic. How to structure and how to define tests?
- unit tests don’t depend on spring boot classes
- integration tests do; they either:
- test servlet layer (test annotated
@WebMvcTest
with autowiredMockMvc
) These tests hit the APIs, pass the path parameters usingMockMvcRequestBuilders
and verify the status response codes and response content usingMockMvcResultMatchers
andMockMvcResultHandlers
. - data tests (question: how to properly test? how to setup the database? how to use
testcontainers
?)
- test servlet layer (test annotated
See this spring boot testing miniseries by Arho Huttunen and his blog for in-depth explanation of different types of testing a spring-boot application.
Building Uris
Don’t forget to properly build uris (e.g. when constructing Location
header):
|
|
Expected exception
Reminder on how to test application exceptions in JUnit5:
|
|
Hardcoding
My current solution can be improved by:
- writing docker-compose.yml such that it also uses environment variables and not hardcoded values for port mapping
- use the outside port in my (p)react client code that connects to that outside-facing service
- define an environment variable which says on which port my (p)react client is accessible as an external service (and perhaps then a port mapping for spring-boot app is not needed at all? - well, it would be nice to have e.g. monitoring entry point)
And it would be nice if I could use a dockerized DNS server so that I can have nice domain names. I wonder if this is doable. Let’s check it out…
Why useEffect is running twice?
This answer: useeffect-is-running-twice-on-mount-in-react explains a bit and moves on right direction, which is: go learn from the official documentation about effects
How to run POST from commandline
I’m always struggling with formatting json in bash for curl consumption so I immediatelly instaled http
which comes from httpie.
Thanks to http
I can run
|
|
Why npm build script hits 8080
I’m not sure why it happens but the Reeac app built using npm build
and started by exposing ./build in, let’s say, python3’s http.server , would nicely use environment variable passed to python process (perhaps this is not what happens).
This works: (i.e. hits 5000 when fetching data)
|
|
but this don’t:
|
|
Investigation
Well, the reason is I should have build the app in proper environment first and then run it even withour that enviroment (previous values would be baked into the build app). So proper sequence of operations is:
|
|
Separate .env
- there may be more than two .env files (for example: for two different sets of variables which we want to apply for development and production) (see this SO answer)
How to correctly build react app image?
Inspiration: Creating your first react app using Docker
To be honest, I don’t “feel” how shoudl I use Docker:
- should I build everything locally and
docker-compose up
only when the build is done or - should I leave the building to the container
How to dockerize Spring Boot?
I tried to build java app in Docker container in such a way that I copy pom.xml
and .mvnw
scripts and then let the maven build the app, however, downloading Spring Boot dependencies took so much time that now I simply build locally the jar and rebuild the image using that changed jar.
The proper way to do this, I guess, is to
- use layered jar as explained in spring.io blog and then
- use multi-stage docker file to extract and copy separate layers
Summary
I have learned a lot. I’ve discovered huge areas of things I don’t know. I have practiced React and tried Docker. This is a green field for me, so I was having fun…
Ten wpis jest częścią serii docker.
- 2022-21-09 - Dockerize Spring Boot App with Frontend in React
- 2022-15-09 - Docker Volumes
- 2022-28-04 - Dockerize Spring Boot REST Service with Postgresql on Docker