In the first part of this tutorial, we created a Todo application with a React front end and a Spring Boot back end. In the second part, we added a local MongoDB instance for storage. Now we’ll move the application into a production environment using MongoDB Atlas, Vercel for the React front end, and a Spring virtual machine to host the back end. We’ll also dip into some considerations around supporting HTTPS with a certificate for the back-end API.
The deployment architecture
When you get ready to deploy an application into production, the usual thought is: what’s the easiest way to meet all the application requirements? In this case, we have three components that need to interact: MongoDB, the Spring server, and the React front end.
We’ll use the following technologies to accomplish our goal:
- A MongoDB Atlas managed instance for the datastore
- A Spring Web API VM on Google’s public cloud for the Spring server
- A serverless deployment on Vercel for the React/NPM front end
There are advantages and disadvantages to these choices, as there would be with any component we choose. Probably the biggest point for our stack here is that the parts that can be simple—the database and front end—are hosted by services that make deploying them very easy. The Java middleware, where we want more control, is deployed into a virtual machine which gives us more direct access to things.
I should also mention that we won’t devote any attention to creating a CI/CD pipeline for this example. That could easily fill another entire article. We’ll just work on getting the components into a reasonable configuration that could be developed into a more effective build-and-deploy setup with minimal effort. We’ll use a self-signed certificate for the Java API.
The database
We begin with the lowest level of the stack, the MongoDB datastore, because the Spring API will depend on knowing its location. In turn, React will need to know where the Spring application is.
MongoDB Atlas is the official managed platform from the makers of the open source MongoDB that we used for our local development install in Part 2. Like MongoDB, Atlas is geared towards making the developer’s life easy. You can sign up for a free account here.
We will make use of a free cluster for our purposes. From the Atlas dashboard, once you have your free account, click the Create button, as shown in Figure 1.
Matthew Tyson
Next, follow the prompts to create a free-tier cluster. The new cluster will appear as mine does in the above screenshot, as “InfoWorld.” You will need to create a username and password to access this instance, so go ahead and write those down. If you forget, you can reset the password by navigating to Security->Database Access on the left-hand menu.
To find the connection string that we’ll use from Java to access the database, click on the cluster instance, then select the “Drivers” option, and then pick from the dropdown to find Java, which will give you a string to copy:
Matthew Tyson
My connection string looks like so:
mongodb+srv://:@infoworld.mwvvuo9.mongodb.net/?retryWrites=true&w=majority&appName=infoworld
Just hang on to that for now. We’ll use it soon.
The Spring application
We need to take several steps to prepare the Spring application for production:
- Create two profiles:
dev
andprod
. - Create a virtual machine to host the application.
- Extract the username and password to an environment variable.
- Add the MongoDB connection string to the
prod
profile using the environment variables. - Create an HTTPS certification (cert) to support HTTPS.
- Create a standalone JAR build, then copy it to
prod
and run it.
I am using Google Cloud Platform for the virtual machine, but the steps will be similar for any cloud provider: sign up for an account, then go to the Compute menu to define and create a new virtual machine. Most cloud platforms have free-tier VMs you can use for this tutorial. Once you have provisioned a VM, you can SSH to it and check out the Spring app from my GitHub repository for this tutorial:
$ git clone https://github.com/MTyson/java-spring-react.git
That will give you the version that already has the necessary changes. Let’s walk through them.
Note that there is a keystore file at src/main/resources/keystore/infoworld.p12
. This is the way Java apps store keys for use in Transport Layer Security. We need this for serving the API over HTTPS, which we’ll need because Vercel will host our React front end with HTTPS, and browsers won’t serve mixed security content. We want to host production APIs on HTTPS in any case.
We are using a self-signed certificate, which means we’ll still get a warning in the browser, but it gives you an idea of how things work. See HTTPS using self-signed certificate in Spring Boot to learn how to generate your own self-signed certificate. You’ll end up with a keystore file like the one here, and because it’s in the /resources directory, it will be available on the classpath. If you obtain keys signed by an authority, you can use those and avoid the warning. (Let’s Encrypt will provide you with legitimate keys, but only for a domain name.)
There are various levels of sophistication in how you store your keys, including using a centralized cloud store like GCP KMS. We’re keeping it simple here.
Application properties and profiles
We need two profiles: prod
and dev
. We can create them by adding two files to /src/main/resources
:
application-dev.properties
for thedev
profile propertiesapplication-prod.properties
for theprod
profile properties
Our property files need to do two things: point at the right database and configure the TLS key for HTTPS. The dev
file will point to the local MongoDB database and the prod
file will point to MongoDB Atlas. (For a more in-depth discussion of Spring profiles, see Mastering Spring Boot profiles in application.properties.)
Here’s a look at our application-pro.properties
file:
spring.application.name=demo
spring.data.mongodb.uri=mongodb+srv://${MONGODB_USERNAME}:${MONGODB_PASSWORD}@infoworld.mwvvuo9.mongodb.net/todo?retryWrites=true&w=majority&appName=infoworld
server.ssl.key-store-type=PKCS12
server.ssl.key-store=classpath:keystore/infoworld.p12
server.ssl.key-store-password=${KEYSTORE_PASSWORD}
# Alias is set when you create the keystore
server.ssl.key-alias=infoworld
server.ssl.enabled=true
You’ll notice we are relying on environment variables to set the username and password for MongoDB and the password for the keystore. This is to keep those credentials out of version control. In Unix-like systems, you can add these to ~/.bashrc
like so:
export MONGODB_USERNAME=dbname
export MONGODB_PASSWORD=dbpass
export KEYSTORE_PASSWORD=keystorepass
Now the Spring application is ready to run in production mode. For testing, you can run the prod
profile in development mode like so:
$ mvn spring-boot:run -Dspring-boot.run.profiles=prod
What we want is to package up the profile and deploy it as a standalone JAR. To build the JAR, run:
$ mvn clean package
When the build completes, there will be a JAR file in the target directory. This is the production build that you can run on the VM like so:
$ java -jar -Dspring.profiles.active=prod target/iw-react-spring-0.0.1-SNAPSHOT.jar
Notice we are again specifying the prod
profile but with a slightly different syntax for the JAR file.
When that is all running properly, Spring will expose the API and talk to MongoDB Atlas for persistence. At this point, you could go to the IP address of your VM (you can find this on your cloud provider’s dashboard) and get back the JSON for any to-do items entered:
https://:8080/todos
This will likely return you an empty array: []
.
Notice we’re using HTTPS now. The browser will flag the address as “not secure,” because of the self-signed cert.
React
On to deploying the React app. The first step is to create a Vercel account if you don’t have one. Once you are logged into Vercel, you can click Add New from the project dashboard, and from there you will select the front-end project in GitHub:
Matthew Tyson
The project that holds our React front end is iw-react-java-part3-frontend. You can import that because it is public, however, Vercel won’t do automatic redeploys for you because you won’t have push access. If you clone it, then you can get the full auto-deploy experience.
Vercel makes this very straightforward. Once the GitHub repo is imported, Vercel automatically deploys it. It’ll give you a summary screen for the project:
Matthew Tyson
If you open the app, everything will almost be working. We need to tell the Spring application where our incoming requests are coming from to get around cross-origin restrictions in the browser.
Copy the address for the Vercel front end by opening it and looking at the address bar. Returning to Spring, add that domain to the allowed origins in the controller:
package com.example.iwreactspring.controller
//…
@RestController
@CrossOrigin(origins = "https://iw-react-java-part3-frontend-vizl.vercel.app")
public class MyController { … }
Now rebuild Spring and run again. You should only have to do this once, but it is admittedly clunky. To improve the situation, we could extract the Vercel front-end hosting location to a property and inject that into the controller, so we won’t have to modify the code if the location changes.
If you run into the browser disallowing the self-signed certificate, check out this resource. The workaround is to give the browser permission to accept self-signed certs for your VM’s domain.
Ultimately, you’ll be presented with a fully working application that uses MongoDB Atlas, a GCP virtual machine, and Vercel to host the three components, as shown here:
Matthew Tyson
Conclusion
The biggest shortcut we have taken here is regarding the self-signed certificate. Otherwise, these components are all in legitimate production hosting environments. Of course, a lot is to be desired for clean ongoing operations. We could devote considerable attention to making these components simple to deploy and test and ensure smooth releases.
The downside of this setup is the inherent network calls across the components. We could reduce those by self-hosting everything within our public cloud. On the upside, we have highly isolated services at each component, supporting an easier separation of teams, projects, and deployment pipelines.