Package Your WebApp

Package Your WebApp

So you’re building modern web application. That most likely means you’re building a Single Page Application (SPA) in JavaScript and reading data from a server via REST. The REST server code could be implemented using any number of programming languages and technology stacks.

There are a few schools of thought when it comes to developing a web application. One option is to keep the development of client and server code completely separate. Another approach is to develop both client & server code together via Universal Javascript. Additionally there are issues regarding how to store the code base(s) in the repository, how versioning is applied, and finally how the code is deployed and maintained.

This article proposes a solution that has worked well for one of our projects at Volume Integration. I’ve created a sample application that demonstrates some of the key components of this solution.

You can download/clone the project here:

https://github.com/marshallformula/packaged-webapp

2015 Utah State Park Attendance Example Application

The sample application is a very simple web application that shows a graph of Utah State Park attendance for 2015. Here is a screenshot of the finished product.

Sample Web Application Screenshot

The main components of the application are:

Standalone Java Web Server (Spring Boot). The main purpose is to provide a set of REST services for the WebApp to consume. But it also initially serves the static web application code (HTML, CSS, JavaScript)

Web Application (SPA). The web application uses modern web application practices – including transpiling ES2016 code using Babel, packing and optimizing code and dependencies using webpack, as well as compiling advanced css using preprocessors like less and sass.

Requirements

Development of this application requires the following:

Developing the Application

This application is set up so that you can develop the REST services and JavaScript application independently.

Developing REST Services

The REST services are written in Java Utilizing Spring Boot & Spring MVC functionality. All of that code is located in src/main/java.

To develop the services code interactively just run

gradlew bootRun

This will start up the embedded webserver (Tomcat by default) and deliver your services. As you write your code – the server should detect code changes and restart as necessary due the inclusion of Spring Boot DevTools.

If you are developing/running the REST server interactively while developing the JavaScript web application – you will need to add a system property like so:

gradlew bootRun -Dcors.origins=http://localhost:3000

There is one hack feature that’s required to enable the bootRun gradle tasks to accept and apply configuration properties in this manner. Add this snippet to the build.gradle file:

1
2
3
4
bootRun {
systemProperties System.properties
}

This is because when developing the web application – it will be running on its own development server on port 3000 (see below) which will have a different host and we will need to configure CORS to allow the web application to consume the REST services.

I won’t delve into the all of the intricacies of how Spring MVC works its magic, but the following configurations are required in the app to make it work:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@SpringBootApplication
public class ExampleApplication extends WebMvcConfigurerAdapter {
public static void main(String[] args){
SpringApplication.run(ExampleApplication.class, args);
}
@Value(“${cors.origins}”)
private String origins;
//this is to add allow the CORS origins specified on the cors.orgins property to communicate with this server.
@Override
public void addCorsMappings(CorsRegistry registry) {
if(!StringUtils.isEmpty(origins)){
CorsRegistration registration = registry.addMapping(“/api/**”);
Arrays.stream(origins.split(“,”))
.map(String::trim)
.forEach(registration::allowedOrigins);
registration.allowedMethods(“GET”, “POST”, “PUT”, “DELETE”);
} else {
super.addCorsMappings(registry);
}
}
//this is necessary to forward all un-mapped requests to index.html.
//This is required if you want to use the HTML5 History API
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.setOrder(Ordered.LOWEST_PRECEDENCE);
registry.addViewController(“/**”).setViewName(“forward:/index.html”);
}
//this is helpful in connection with the method above to allow paths to the /assets folder for images, files etc
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler(“/assets/**”).addResourceLocations(“classpath:/static/”);
super.addResourceHandlers(registry);
}
}

Developing the JavaScript Web Application

The JavaScript web application is set up to operate as a standalone web application (when provided some REST services to connect to). All of the web application code is under src/main/app. You will need to be in this directory to run the following commands.

The web application is packaged with webpack and utilizes webpack-dev-server to enable interactive development.

First you must download all of the necessary dependencies from npm.

npm run setup

Once that is complete you are ready to start the development server by simply running npm start. However the application can be configured via an environment variable to connect to a REST server at any location. This way you can work on the web application and connect to any instance of your API (dev/test environments).

If you would like to connect to your local instance of the REST services that are running using the instructions above – you will just need to set the REST_URL environment variable to http://localhost:8080/api. The easiest way to do that is to just prepend the variable declaration to the start command like this:

REST_URL=http://localhost:8080/api npm start

You might wonder how an environment variable on the can be incorporated into the necessary places in the client JavaScript files. There are most likely several ways to do this – one of the simplest is through the webpack DefinePlugin.

Just add & configure the plugin in the webpack.config.json file like this:

1
2
3
new webpack.DefinePlugin({
REST_URL : JSON.stringify(process.env.REST_URL || “/api”)
})

The application is transpiled, packaged and available at http://localhost:3000. This is why the CORS property must be configured properly above.

The development server communicates with your browser via web sockets – so any changes that are made to your code are immediately re-packaged and available to your browser without needing to refresh. Like Magic!

Building the application

The application is packaged together as a Spring Boot runnable jar compiled using Gradle. Installing Gradle manually is not necessary. The application is configured using the gradle wrapper script.

To compile and package the application just run this:

./gradlew bootRepackage on OSX/Linux

gradlew.bat bootRepackage on Windows.

This will download all dependencies, compile/transpile and package all of the code for both the Java REST application and the JavaScript web application into in a runnable jar. The jar file is located in build/libs/packaged-webapp-1.0-SNAPSHOT.jar. This is accomplished by the very helpful gradle plugin that runs npm scripts. The build npm script inside our web application’s package.json will transpile and package all of the front end code as necessary and place it in src/main/resources/static – from which the Spring Boot application is preconfigured to serve static content.

The key is to add the proper gradle build dependencies to run the npm scripts before packaging the entire application into a jar. This is done with the following code in the build.gradle file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
task buildApp(type: NpmTask) {
args = [‘run’, ‘build’]
execOverrides {
it.workingDir = ‘src/main/app’
}
}
task npmClean(type: NpmTask) {
args = [‘run’, ‘clean’]
execOverrides {
it.workingDir = ‘src/main/app’
}
}
clean.dependsOn npmClean
bootRepackage.dependsOn buildApp

After it is packaged running the application is simple:

java -jar build/libs/packaged-webapp-1.0-SNAPSHOT.jar

This will start an embedded webserver (Tomcat) which you can access at http://localhost:8080. You can change the port if necessary by adding the --server.port argument:

java -jar build/libs/packaged-webapp-1.0-SNAPSHOT.jar --server.port=8989

Being able to develop the separate application components both individually and independently provides many benefits. We can have both server side and client developers work concurrently in the same code base. This helps in keeping the REST services and Web application in sync.

Also utilizing Spring Boot to package and run the application simplifies both the building and deployment of the application. A simple gradle command compiles, transpiles, and packages all of the code (server code and client code) together. Deployment is simple – because it’s only one simple jar file and the only dependency is Java. No mucking about with slightly different servlet container configurations on different environments etc.

I’m sure there are other great solutions out there that help ease the burden of developing server/client web applications together and we’d love to hear about them. Let us know in the comments.

If you have any questions about how we’re making this work or questions about the example project feel free to reach out:

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

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