Deployment ready websites with Spring Boot

· 9 minute read
Spring Boot

Spring Boot is fast becoming an industry standard for building production-ready web applications, from RESTful Web Services to Enterprise Website Platforms. In this post I will detail the steps required to create a development environment that manages dependencies, continuously integrates, and automatically packages both the back and front end of your web platform in just one terminal command. The result is a single .jar file that can be deployed straight into production and ran with your typical java -jar terminal command.

Want to skip the tutorial? Check out the full source code for this project on GitHub.

The front end

For the front end of our website we may want to include various third party CSS and JavaScript libraries, such as normalize.css for ensuring our site renders the same across all browsers, or highlight.js to add support for code syntax highlighting. We could simply download the distribution packages for these libraries and redownload them every time they update, but we want our website to automate that for us.

Front end dependencies

npm lets us manage our front-end dependencies by creating a package.json file that details the dependencies we require (with the version we want). We can then run npm install for it to download all of our dependencies and place them in a directory named node_modules.

The package.json file should be placed in the root of our front-end module (i.e. ./frontend/package.json) and should look something like this:

{
  "name": "my website",
  "scripts": {
    "build": "gulp build"
  },
  "dependencies": {
    "gulp": "*",
    "gulp-less": "*",
    "gulp-minify-css": "*"
  },
  "private": true
}

As you can see there are a few packages in the example package.json file already, as well as a build script. These are related to the build automation that we want the front-end to perform, as so far we have simply downloaded the dependencies and done nothing with them.

Build automation

Gulp

For our build system we are going to use Gulp. Gulp allows us to define a variety of tasks that should be performed during the build process, such as CSS minification and JavaScript transpiling. This will result in us being able to merge and minify all of our stylesheets and JavaScript dependencies, plus our self written stylesheets and scripts, into a set of small optimized files.

Gulp tasks are written in the gulpfile.js file, which should reside in the source of the front-end module (along with package.json). In this file we can construct a few simple tasks to deal with all of our front-end source files.

Styling

Less

For our website’s styling we are going to use Less which allows us to extend further than a typical Cascading Style Sheet, facilitating the use of variables, mixins, and functions.

In this demonstration we will simply style a header to be coloured red, via Less:

h1 {
  color: red;
}

To process our stylesheets we need to add it to our build task in Gulp. This can done via the gulpfile.js file. For the processing we are going to have Gulp search recursively for any .less files in ./frontend/src/less, minify them, convert them to CSS files then finally place them in our build/dist folder. The task is shown below:

'use strict';

var gulp = require('gulp');
var less = require('gulp-less');
var minify = require('gulp-minify-css');

gulp.task('less', function () {
    return gulp.src('./src/less/**/*.less')
        .pipe(less({
            paths: [
                './src/css/'
            ]
        }))
        .pipe(minify({processImport: false}))
        .pipe(gulp.dest('./build/dist/css/'));
});

gulp.task('build', ['less']);

Front end packaging

In order for our front-end to be a dependency for the back-end we must first package it into a single .jar file that the back-end can add to its classpath. This can be achieved with Gradle and the Gradle Plugin for Node. This plugin will allow use to execute tasks in our package manager through Gradle.

Gradle

The build.gradle file for the front-end (located at ./frontend/build.gradle) will allow us to define our build tasks as well as the version of node.js and npm we want our packaged file to use. The packaged version of the front end will only include the files we generated in our ./frontend/build/dist folder, thus won’t needlessly include our Less source file.

It should look something like this:

buildscript {
    repositories {
        maven {
            url "https://plugins.gradle.org/m2/"
        }
    }
    dependencies {
        classpath "com.moowork.gradle:gradle-node-plugin:0.10"
    }
}

apply plugin: "com.moowork.node"

node {
    version = '0.10.29'
    npmVersion = '1.4.21'
    download = true
}

jar {
    from 'build/dist' // only include files located in build/dist to avoid including the entire frontend src

    // Jar contains duplicate empty folders, see Gradle issue:
    // http://issues.gradle.org/browse/GRADLE-1830
    // so we need to set includeEmptyDirs to false
    includeEmptyDirs = false
}

task gulpBuild(dependsOn: npmInstall, type: NpmTask) {
    args = ['run', 'build']
}

jar.dependsOn gulpBuild

Now our front-end module is ready to be packaged and utilised by the back-end as a dependency.

The back end

As previously mentioned, the back-end of our website will be a Spring Boot application. The application will serve HTML5 pages that are parsed by our template engine. For this article I am going to use the FreeMarker Template Engine, as Spring Boot provides it’s own auto-configuration that will result in less time spent configuring and more time writing templates.

Back end dependencies

Similarly to the front-end, the back-end will make use of Gradle as both a build system and dependency manager. Dependencies we will need include the FreeMarker Template Engine and the Spring Boot Web Starter. We must also apply the Spring Gradle Plugin to fully utilize Spring’s packaging and dependency version management, allowing us to omit version numbers from our dependencies (as Spring knows what version to grab automatically).

Our build.gradle file for the back-end should look like this:

buildscript {
    ext {
        springBootVersion = '1.3.0.M5'
    }

    repositories {
        mavenCentral()
        jcenter()
        maven { url "https://repo.spring.io/snapshot" }
        maven { url "https://repo.spring.io/milestone" }
    }

    dependencies {
        classpath group: 'org.springframework.boot', name: 'spring-boot-gradle-plugin', version: springBootVersion
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'

jar {
    baseName = 'website'
}

sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
    mavenCentral()
    maven { url "https://repo.spring.io/snapshot" }
    maven { url "https://repo.spring.io/milestone" }
}

dependencies {
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-freemarker'
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-web'
    testCompile group: 'org.springframework.boot', name: 'spring-boot-starter-test'
}

We must also not forget to add the front-end as a dependency of the back-end, which can be configured in the project’s root build.gradle file:

subprojects {
    apply plugin: 'java'
}

project(':backend') {
    dependencies {
        compile project(':frontend')
    }
}

Templating

As we styled a simple <h1> tag in our Less stylesheet we must test that our styling is being correctly applied (as this will confirm our back-end is correctly applying the front-end styling rules).

Below is an example template file that should render a simple "Hello World" message, hopefully soon in red.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <link rel="stylesheet" type="text/css" href="/css/stylesheet.css" />
    <title>Hello World</title>
  </head>
  <body>
    <h1>Hello World</h1>
  </body>
</html>

We must now tell Spring Boot about the / endpoint, and let it know what template file to use. We can do this with a Controller that simply maps the / endpoint to the name of a template. In our case, Spring Boot will look in the templates directory for any files suffixed with .ftl (indicating a FreeMarker template), thus we can give it the name of our index template:

@Controller
public class IndexController {
    @RequestMapping("/")
    public String index() {
        return "index";
    }
}

Tying the loose ends

Now it is time for us to wire up the back-end to access files in our front-end package. This can be achieved two ways, we can tell the back-end to look on our local file system under the ./frontend/ module, or we can package the front-end into a .jar and add it to the classpath of the back-end. The former of these options is useful in a development environment, as we can change files locally and see the differences without having to repackage the entire front-end. The latter option is useful in a production environment as when the back-end is packaged into a jar itself, the front-end resources it requires will not be on the local file system (and must be added to the classpath to be accessible).

This can be configured with a file that extends WebMvcConfigurerAdapter, as we can add resource handlers that tells the application where to look for files (such as our front-end files). As previously discussed we want the application to find resources in a different location dependant on the current development environment. We can distinguish a development environment by using the active profiles configuration in Spring. By default Spring looks in the resources folder for a file named application.properties for such configuration, but we can also apply them retroactively via a command line switch.

Below is our application.properties file configured to run in a development environment. You can either remove this line or change "development" to "production" before you deploy. Don’t worry, however, if you forget to change it before you package the front-end, as you can simply add --spring.profiles.active=production to your typical java -jar command to override the value set in our application.properties file.

# Indicate we are working in a development environment
spring.profiles.active=development

With our environment now configured, we must now tell Spring Boot where to look for front-end resources for each of these profiles. As previously stated, when running in the development environment we should look on the local file system, and when running in production we should look on the classpath.

@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    @Autowired
    private Environment environment;

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        String frontend;

        if (environment.acceptsProfiles("development")) {
            String workingDirectory = System.getProperty("user.dir");

            /*
             * if we are running via gradle bootRun then the working directory is
             * suffixed with /backend and thus won't find resources correctly
             */
            if (workingDirectory.endsWith("/backend")) {
                workingDirectory = workingDirectory.substring(0, workingDirectory.lastIndexOf("/backend"));
            }

            /* find front-end build files from the local file system */
            frontend = "file:///" + workingDirectory + "/frontend/build/dist/";
        } else {
            /* find front-end build files from the classpath */
            frontend = "classpath:/";
        }

        registry.addResourceHandler("/**").addResourceLocations(frontend, "classpath:/static/");
    }
}

Deployment

With both the front-end and back-end wired up it’s now time to see if the back-end template is being affected by our front-end styling. To package the back-end (which will include the front-end as a dependency), we can run the bootRepackage Gradle task: gradle bootRepackage. This will give us a single .jar file, less than 15MB in size!

Now we can run the .jar, but in the likely scenario that we forgot to change our development environment to production, we must override it when executing the jar.

Whilst in the backend directory of the project, we can now run the following terminal command:

java -jar build/libs/website.jar --spring.profiles.active=production

If everything has gone smoothly you should see the following output, which indicates our web-server is now successfully listening on port 8080:

Command line

And finally, opening our browser to http://localhost:8080 shows us our red <h1> tag:

The site