A Minimalist Guide to Building Spring Boot Angular 5 Applications


Over the last year, I have worked with many teams using Spring Boot with Angular/React for developing Java web applications. I consider Spring Boot a pragmatic opinionated way to build Spring applications. Spring Boot makes it easy to build web applications in Java and provides a productive environment for development.

One thing that I observed is that many teams have stopped applying good practices while building Spring Boot applications. The practices that I am talking here include modularising codebase, enabling static code analysis tools for code quality, release management, different modules for frontend and backend code, etc. Everyone wants to use Microservice architecture when they don’t even understand how to build modular applications. Yesterday, I was talking to a startup that has yet to write single line of business code but has wasted last many days finalising their cool technical stack(Next.js, GraphQL, React, Relay, Node, etc.). I don’t know where we are going with all this BS but that is a rant for some other day.

In this post, I will walk you through setting up Spring Boot Angular 5 project for building production grade applications. We will make use of Maven build tool. We will create separate modules for frontend and backend so that frontend and development teams can easily work on the project. In many projects, I saw people keeping frontend assets in src/main/resources. It then becomes difficult to use build tool and package manager for frontend code.

Prerequisite

To follow along you will need following on your machine.

  1. Java 8: Download and install the JDK from official website.
  2. Node.js : Download and install the latest version of node.
  3. Angular CLI: It is the official way to bootstrap Angular applications. You can install it using your favourite package manager. For npm, you can type npm install -g @angular/cli on your command-line. If you use yarn, you can type yarn global add @angular/cli on your command-line.

Now that we have installed all the prerequisites let’s get started.

Create a multi-module Spring Boot Angular 4 application

In the remaining part of this post, we will create a multi-module Spring Boot application. There will be two modules:

  1. backend: This contains Spring Boot based backend written in Java.
  2. frontend: This contains Angular 5 based frontend of the application.

By the end of this post, you will be able to build the whole application as a single fat jar using Maven.

Step 1: Create Spring Boot project using start.spring.io

The easiest way to create a Spring Boot project is to use Spring Initializr project available at http://start.spring.io .You can use either the web user interface or tool like cURL to create the project. Below is the cURL command that will create the starter project in the myapp directory.

$ curl https://start.spring.io/starter.zip \
-d dependencies=web,data-jpa,devtools,h2 \
-d groupId=com.shekhargulati -d artifactId=myapp -d name=myapp \
-d description="Spring Boot Angular 5 Application" -d baseDir=myapp \
-o myapp.zip && unzip myapp.zip && rm -f myapp.zip

Please note that you don’t have to type $. $ signifies shell prompt.

You need to have unzip utility installed on your machine. If you don’t have required tools, then you can create app using http://start.spring.io/ web interface.

The above will create Apache Maven based Spring Boot project. The project uses maven-wrapper that provides an easy way to ensure a user of your Maven build has everything necessary to run your Maven build. We have specified following dependencies:

  1. web: As this will be a web application so we have specified web as our dependency. This will add spring-boot-starter-web dependency to project’s build file pom.xml. The
  2. data-jpa: The application will store data in PostgreSQL. The data-jpa will add spring-boot-starter-data-jpa to the pom.xml file. Spring Data JPA makes it easy to work with relational database.
  3. devtools: This adds hot reloading to the application. This makes development more productive.
  4. h2: This adds in-memory database h2 dependency to the pom.xml. This is essential for writing integration tests.

To build the project you can run the following command.

$ cd myapp
$ ./mvnw clean install

If you are Windows user, then you can the following command.

$ mvn.bat clean install

The above command will first download the required Maven version and then build the whole project.

To run the project, you can use ./mvnw spring-boot:run command.

Step 2: Convert to a multi-module Maven project

The project generated by Spring Initializr has a single module. As mentioned above, we need a multi-module project so we will update our project structure to make it a multi-module project with two modules — backend and frontend.

Change directory to myapp and create the following directory structure. On *nix systems, you can run mkdir backend frontend.

├──.mvn
    └── wrapper
      ├── maven-wrapper.jar
      └── maven-wrapper.properties
├── backend
├── frontend
├── mvnw
├── mvnw.cmd
├── pom.xml
├── src
│   ├── main
│   └── test

Update the pom.xml by adding frontend and backend project to the modules section as shown below.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.8.RELEASE</version>
        <relativePath/>
    </parent>

    <groupId>com.shekhargulati</groupId>
    <artifactId>myapp</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>pom</packaging>
    <modules>
        <module>frontend</module>
        <module>backend</module>
    </modules>

    <name>myapp</name>
    <description>Spring Boot and Angular application</description>

</project>

In the above shown pom.xml, we also updated packaging to pom. pom acts as a container of submodules, each submodule is represented by a subdirectory in the same directory as pom.xml with pom packaging.

Move the src directory to the backend directory and create a pom.xml with the content as shown below.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.shekhargulati</groupId>
        <artifactId>myapp</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <artifactId>myapp-backend</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

We added all project dependencies to thebackend pom.xml as this module will house our backend code. This is identical to the pom.xml created with Spring Initialzr expect that we have to add parent section. The parent pom for myapp-backend module is identified by groupId com.shekhargulati and artifactId myapp.

Now, we will update the frontend module. Create a new pom.xml inside the frontend directory and populate it with following contents.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.shekhargulati</groupId>
        <artifactId>myapp</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <artifactId>myapp-frontend</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

</project>

After making the changes, run the ./mvnw clean install from the project root. The whole build should run fine and you should see output as shown below.

[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO]
[INFO] myapp .............................................. SUCCESS [  0.228 s]
[INFO] myapp-frontend ..................................... SUCCESS [  0.508 s]
[INFO] myapp-backend ...................................... SUCCESS [  4.735 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.739 s
[INFO] Finished at: 2017-11-08T16:59:25+05:30
[INFO] Final Memory: 36M/324M
[INFO] ------------------------------------------------------------------------

Step 3: Create Angular 5 project

In the previous step, we created the multi-module Maven Spring Boot application but we haven’t yet created the Angular project inside the frontend module.

Go inside the frontend directory and create a new Angular project using the Angular CLI.

$ ng new myapp --directory .

This will generate the sample project in the frontend directory and install all the dependencies using npm package manager. You can run the application using ng serve command.

$ ng serve -o

The above command will start the application locally at http://localhost:4200/. You can also run the application using npm or yarn. Npm users can use npm start . Yarn users can use yarn start to start the application. You can learn more about the default project generated by Angular CLI by reading the documentation.

Step 4: Build frontend code using Maven build tool

In the previous step, we created the Angular project using the Angular CLI. The project can be built and run using the package manager of your choice or using the ng command-line. In development mode, you would use those methods, but during deployment both should be packaged as a single artifact. This means we should be using a single tool to build the application. The single tool in our case is Apache Maven. In this step, we will make both frontend and backend modules bundle as a single JAR.

To run the yarn commands using the Maven build tool, we will use frontend-maven-plugin. This plugin comes with a set of built-in goals that we can use for triggering different yarn commands. Replace the content of frontend/pom.xml with the content shown below.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>com.shekhargulati</groupId>
        <artifactId>myapp</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
  <modelVersion>4.0.0</modelVersion>

  <artifactId>myapp-frontend</artifactId>

  <build>
    <resources>
      <resource>
        <directory>dist</directory>
      </resource>
    </resources>
    <plugins>
      <plugin>
        <groupId>com.github.eirslett</groupId>
        <artifactId>frontend-maven-plugin</artifactId>
        <version>1.6</version>
        <configuration>
          <nodeVersion>v8.9.0</nodeVersion>
          <yarnVersion>v1.3.2</yarnVersion>
        </configuration>
        <executions>
          <execution>
            <id>install node and yarn</id>
            <goals>
              <goal>install-node-and-yarn</goal>
            </goals>
            <phase>generate-resources</phase>
          </execution>
          <execution>
            <id>yarn install</id>
            <goals>
              <goal>yarn</goal>
            </goals>
            <configuration>
              <arguments>install</arguments>
            </configuration>
          </execution>
          <execution>
            <id>yarn lint</id>
            <goals>
              <goal>yarn</goal>
            </goals>
            <configuration>
              <arguments>lint</arguments>
            </configuration>
          </execution>
          <execution>
            <id>yarn build</id>
            <goals>
              <goal>yarn</goal>
            </goals>
            <phase>generate-resources</phase>
            <configuration>
              <arguments>build</arguments>
            </configuration>
          </execution>
          <execution>
            <id>yarn test</id>
            <goals>
              <goal>yarn</goal>
            </goals>
            <phase>test</phase>
            <configuration>
              <arguments>test --watch false</arguments>
              <failOnError>true</failOnError>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

This plugin will first download and install node and yarn on your machine and then it will invoke different goals yarn install, yarn lint, and yarn build during the Maven build lifecycle.

You can run the build of the frontend code using the ../mvnw clean install command. It will invoke yarn commands to build the project. The build output will be placed inside the dist directory of frontend module.

One interesting thing to note here is that we are packaging application as a WebJar. WebJar allows you to package your client side dependencies as a JAR file. Spring Boot is preconfigured to handle them. It will serve the index.html in the jar’s META-INF/resources directory. I find this a clean way to package frontend code.

One last change that you have to make is change the build output directory. This is required because WebJar package static content in the META-INF/resources directory. Update your package.json build script with the one show below.

"build": "ng build -prod –output-path dist/META-INF/resources"

Now, build the full application by executing ./mvnw clean install command inside the myapp directory. This will build the Java executable Jar in the backend/target directory. You can run the jar as shown below.

$ java -jar backend/target/myapp-backend-0.0.1-SNAPSHOT.jar

You can view the application by firing up your favourite browser http://localhost:8080. You will see Angular application rendered as shown below.

Spring Boot Angular 5 Starter

To make it easy for you, I have created a Spring Boot Angular 5 starter project that add more goodies to the stuff covered in this blog. You can give shekhargulati/spring-boot-maven-angular-starter a try. There are many useful features in the starter as mentioned below:

  1. Multi module Maven project: A multi module project to modularize backend and frontend code separately.
  2. Maven wrapper: So, you don’t need to install Maven on your machine.
  3. Checkstyle: Enforce sane coding standard guidelines.
  4. ErrorProne: Find errors in your code.
  5. Frontend packaged as a WebJar.
  6. CORS enabled: A global configuration is added to enable CORS so that frontend can work seamlessly with backend during development.
  7. REST API base path: Sets the base REST API path to /api. You can configure it by changing rest.api.base.path property.
  8. Maven release plugin
  9. CI: The project is preconfigured to use TravisCI as continuous integration server.

Conclusion

In this post, you learnt how to create an Apache Maven multi-module Spring Boot application. Your learnt how to create Angular 5 application and bundle it as a single application using the frontend-maven-plugin. Now, you have the strong base on which you can start building your application.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s