Skip to content

Modern Spring Boot Web App Architecture

Web development has changed a lot over the last decade.

When I started working with Web Based Analytics tools, they were mostly written in Java using JSPs, or some terrible ASP stuff with ActiveX, or flash. Urgh. Luckily for us most of this is a thing of the past.

Soon after that, along came GWT and a few other frameworks released by the big tech corps to try and make Web Apps easier to build. They were okay, but pretty hard to customise.

If you didn’t want to use something like that, you were basically left with HTML, CSS, Javascript and JQuery. Which was fine, but then REST and the like was in its infancy, you had SOAP endpoint and the like. Web app development wasn’t fun, especially if it wasn’t something you did as your day job. Things changed quickly and keeping up with the latest trends in a quickly changing landscape was hard.

Over the more recent years, I’ve been building applications using whatever REST framework was flavour of the month and a HTML5 web application on the front, Angular, React, whatever hip framework was in use at the time.

For work projects, I have no issue in doing this, they are projects that will be maintained, I’m paid to build and maintain them, and so I want to build stuff using the most advanced and flexible frameworks I can. But, I had another project I wanted to build a web app for. The difference with this application was that I don’t do a great deal of development on it and I wanted the ability to make changes to the web application to be as trivial as possible. I don’t want to have to rememeber how Redux works, I don’t want to have to stand up another frontend to work with the backend, I just want to be able to clone the code, hit go and have both the backend and frontend working in unison.

I want to build a UI for Sparkler, which is a webcrawler from the USC Data Science department. It’s open source and lives here: https://github.com/uscdatascience/sparkler. The majority of the code is written in Java or Scala and requires a JVM to operate. Its been a while since I did any real Java framework development, but from my earlier days of Java development I know Spring, and I know Spring Boot has come on a long way since I tried it in the early days. These days there isn’t buckets of XML to setup if you don’t want, annotations wire up most of your code and you get an integrated web server to get going. It’s just easy.. ish.

But as I alluded to earlier, I wanted something good for webapps but easy to maintain. So I set out asking the question, can I create a modern webapp, but not use NodeJS and NPM which just adds to the tooling requirements. I didn’t want to use React, Angular or any trendy framwork but I wanted an application that was reasonably lightweight and easy for non core HTML developers to understand and for me to remember if I dipped back into the code to improve it or add a new feature. I had seen the frameworks coming from the guys who develop Basecamp, Turbo, and Stimulus. Both lightweight Javascript frameworks that you could load from a CDN rather than NPM. Turbo is an interesting framework that can speed up navigation and application updates, removing the need for full page reloads by crafted HTML tags, less crazy Javascript and more HTML code, designed for serverside rendering and streaming of HTML. This means I can update the UI with minimal code when the application updated, the guys at Basecamp use Rails, but surely this stuff will work with Java, I thought!

Spring comes with support for Thymeleaf which is a templating engine which allows you to write HTML fragments and integrate them into your main pages. They also allow for variable replacement and much more. This gives me the flexibility I was looking for, because its server side rendering of the HTML and not me building some crazy REST endpoints and then some Javascript parsing on the frontend side. Okay, you need to learn some annotations, but its not half as crazy it seems than Redux actions and other React stuff, especially for someone like me who spends most of their time writing code in Java, Python and Go.

So I set out trying to prototype how this would work within a Spring Boot context. Getting all the components integrated and demo code working. How hard could it be, I was after simplicity, right?

First I tested Turbo via CDN and that worked well. Then I tested Stimulus via CDN and that came to life without many issues. I started thinking about what else I wanted out of this platform. I’m not a web designer, so some help with CSS I thought would be good. Bootstrap used to be how you did this, but these days there are so many options. Most, to keep themselves small also use a processor to minify or remove dead code which of course brings me to a conundrum, processors generally require npm.

I mulled it over over the Packers win over the LA Rams and decided that I was going to need some form of NodeJS install to really do what I wanted to do, but I wanted to make this easy. No crazy proxies, no separate build chain for the UI, I wanted the development environment to feel like it was incorporated into the Spring Boot process, not some random thing tacked on the side.

What did I want in the stack?

  • Server side HTML with Spring Boot and Thymeleaf
  • Turbo
  • Stimulus
  • Tailwind CSS

I wanted it simple, and more might come over time, but these were my initial requirements to give me a responsive webapp without writing buckets of CSS along with incorporate into the compiled Jar file when it comes to shipping the platform.

With some googling I came across this fantastic starting point: https://www.wimdeblauwe.com/blog/2019/2019-10-20-spring-boot-and-thymeleaf-with-css-javascript-processing-using-gulp/

Wim has written a lot about Thymeleaf and this usage of Spring Boot with Thymeleaf and CSS Postprocessing was exactly what I needed.

I stuck it in place, and watched the initial build come to life. I could go to http://localhost:8080 and see the Java served web app pages and I could visit http://localhost:3000 and see the Gulp served versions with livereloading and the usual NodeJS niceities.

I started making changes though and found an error where it was complaining about the lack of require() support in the browser. So I did some more googling and found I needed to incorporate browserify into the build chain to translate some of the server side NodeJS into client side Javascript. As ever, as its not my day job, it took longer than I’d hoped. Gone are the days of installing Apache2 and just dumping some HTML into the root folder. But once it started working, the transpiled javascript started working nicely and updating when I made changes.

Next up I wanted to incorporate Tailwind into this build chain for CSS help. Again this involved a few gulp changes. I followed Tailwind’s installation suggestions and used PostCSS as the controller and added a pipe to pipe my CSS through postcss before writing the output to the same target destination as before. After a little fiddling the Tailwind CSS code started appearing in the target directory of my Spring application.

Cool beans!

So to clean up this stuff, I wanted to ensure it was all working. So I removed Turbo and Stimulus from their CDN import and npm installed them to bring them inline with the rest of the UI.

I then fiddled around with the Thymeleaf fragments, that took a little sorting as it was all new to me. Mostly just because I stuck the index file in static not templates which meant it ignored my fragments for the first few attempts. Once that was resolved, it all started coming into life, but Turbo was a bit sad. Weirdly it worked on the Java endpoint but not the NodeJS served live pages. To resolve this I found I had to move the browsersync script tag from the body to the head element. Because this is automatically injected I had to update gulp with the following:

browserSync.init({
        proxy: 'localhost:8080',
        snippetOptions: {
            rule: {
                match: /</head>/i,
                fn: function (snippet, match) {
                    return snippet + match;
                }
            }
        },
    });

The snippet options forces the script into the head block. Having done this my Turbo tests showed my ability to both use internal Turbo linking for zero page refresh updates, I also tested the Turbo streams idea to update HTML elements automatically by streaming HTML over the wire rather than raw data. This worked great! Just define HTML as a response and trigger the API (more on this below).

Turbo Streams in Spring Boot

So Turbo Streams are pretty new and lacking many non Rails examples. So I found a sample and made a few tweaks to build out my own endpoint.

On the server side I created an endpoint like this:

@PostMapping("/greeting2")
	public ResponseEntity<String> pinger(){
		HttpHeaders headers = new HttpHeaders();
		headers.setContentType(CustomMediaType.TURBO_STREAM);
		return ResponseEntity.ok().contentType(CustomMediaType.TURBO_STREAM).body("<turbo-stream action="append" target="pings">n" +
				"  <template>n" +
				"    <li>12345</li>n" +
				"  </template>n" +
				"</turbo-stream>");
	}

As you can see, this is a simple POST with a custom Content Type:

public class CustomMediaType {
    public static MediaType TURBO_STREAM = new MediaType("text", "vnd.turbo-stream.html");
}

This tells Turbo that it can stream the response into the applicaton.

So what does this look like on the frontend?

 <section>
        <h1>Pings section</h1>
        <h2>Turbo Stream</h2>
        <form action="/greeting2" method="post" data-turbo-stream>
            <button type="submit">Ping</button>
        </form>
        <div>
            <p>Ping times</p>
            <ol id="pings"></ol>
        </div>
    </section>

As you can see this form is a simple POST action that hits my /greeting2 endpoint, which is nice with the implementation because although the app is served on https://localhost:3000 it still hits the correct API endpoints without having to install any proxy or doing any base url shifting.

When you hit the button it submits the POST. The response from the server, as you know is of type, vnd.turbo-stream.html, in the response it says to append the id with the content of the template, so in this case the pings ID is the ol – ordered list and so when it processes the response it sticks 12345 into the list, and you can keep pressing and it will keep appending. In all of this I have written no javascript in my web application. The Turbo library has managed all of this for me. It will deal with navigation, it will deal with UI updates. Of course for this there is a time and a place, but for a streaming webapp this seems like a pretty cool way of building minimal frameworks but still fast and responsive. Apparantly, it also supports websockets for endlessly streaming HTML, which sounds extra cool and I look forward to discovering how that works.

Round up

So I didn’t get the no NodeJS web application I was looking for. Technically I could have, but the power of CSS processing is just too large to ignore these days. But I did get the npm build wired into the maven pom, all you do is run mvn clean install and it will download nodejs and do what it has to do, its not looking for crazy command line applications and path stuff, so for those people who don’t do UI dev, this is ideal. Same for CI usage.

I’ve got to dig in properly into building this application out, but these lightweight server interactions were exactly what I was looking for, for someone who dabbles in UI code rather than lives it, it gives me a lightweight webapp with Javascript that is easier to manage than React or the like, not because React is bad, but because for the stuff it needs to do, its complex. Give me a framework that allows me to remove the need to write boilerplate Javascript and I’ll happily use it. Give me templates for easy HTML fragmentation and variable replacement and I will happily build server integrations that give us a functional webapp without all the modern web dev complexity.

You can find my gulp file here: https://github.com/USCDataScience/sparkler/blob/api/sparkler-rest/gulpfile.js and the web template elements here: https://github.com/USCDataScience/sparkler/tree/api/sparkler-rest/src/main/resources/templates

Leave a Reply

Your email address will not be published. Required fields are marked *