Angular + Spring Boot integration using Gradle
Introduction
In this post, I’ll show you how to serve an Angular (>2) frontend application using a Java Spring Boot application as the backend and static page web server, and a Gradle script to perform automated build and deploy tasks both for Spring and Angular.
Requirements
The technology stack required for this tutorial is:
The main goal is to achieve a npm install + build that integrates with Java’s Gradle build and deploys the distribution files into Spring's static resources directory. You could replace Angular CLI for any other Frontend build tool/framework such as React.
Backend
The backend of the stack will be a Spring Boot application.
We can generate a bootstrapped application using Spring's Initializr utility as follows:
Select the basic options for a Gradle Java Project with support for Web and click “Generate Project” to download an initial application.
Next extract the project to a location of your choice.
In order to test frontend and backend integration, we are going to build a simple REST endpoint that generates a “Hello world!” String.
Create a new class (e.g. TestController
) and modify it as follows:
1@RestController
2@RequestMapping(path = "/api")
3public class TestController {
4 @GetMapping(path = "/hello-world")
5 public String helloWorld(){
6 return "Hello world!";
7 }
8}
Frontend
Once the Spring bootstrapped application has been extracted and modified, we must initialize the Frontend application.
In this case, we are going to use angular-cli to prepare a bootstrapped empty Angular application.
The Angular application should be placed in the project’s directory ./src/main/webapp
. The easiest way to achieve this is to open a terminal, navigate to ./src/main
in the project’s root and execute the following Angular CLI command:
1ng new webapp
Angular build configuration
Some additional settings must be made to the default angular-cli.json configuration in order to ease the deployment of the application with Spring Boot.
1{
2 "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 "project": {
4 "name": "Demo Application"
5 },
6 "apps": [
7 {
8 "root": "src",
9 "outDir": "dist/static",
10 "assets": [
11 "assets",
12 "favicon.ico"
13 ],
14 "index": "index.html",
15 "main": "main.ts",
16 "polyfills": "polyfills.ts",
17 "test": "test.ts",
18 "tsconfig": "tsconfig.app.json",
19 "testTsconfig": "tsconfig.spec.json",
20 "prefix": "app",
21 "styles": [
22 "styles.css"
23 ],
24 "scripts": [],
25 "environmentSource": "environments/environment.ts",
26 "environments": {
27 "dev": "environments/environment.ts",
28 "prod": "environments/environment.prod.ts"
29 }
30 }
31 ],
32 "e2e": {
33 "protractor": {
34 "config": "./protractor.conf.js"
35 }
36 },
37 "lint": [
38 {
39 "project": "src/tsconfig.app.json"
40 },
41 {
42 "project": "src/tsconfig.spec.json"
43 },
44 {
45 "project": "e2e/tsconfig.e2e.json"
46 }
47 ],
48 "test": {
49 "karma": {
50 "config": "./karma.conf.js"
51 }
52 },
53 "defaults": {
54 "styleExt": "css",
55 "component": {}
56 }
57}
apps.outDir
property should be changed from “dist” to “dist/static”. Also the project.name
property should be changed from “webapp” to the real name of the application.
Backend integration
In order to test integration with the Backend, we are going to make some simple modifications to the newly created application to connect to the REST endpoint we just created:
1<h1>
2 {{title}}
3</h1>
4<button (click)="sayHello()">Say Hello</button>
5<span>{{result}}</span>
We’ve added to the component’s HTML a button to trigger the REST request, and a placeholder to show the result.
1import { Component } from '@angular/core';
2import { Http } from '@angular/http';
3import { Observable } from 'rxjs';
4
5@Component({
6 selector: 'app-root',
7 templateUrl: './app.component.html',
8 styleUrls: ['./app.component.css']
9})
10export class AppComponent {
11 title = 'app works!';
12 result = '';
13
14 constructor(private http: Http){
15 }
16
17 private sayHello(): void {
18 this.result = 'loading...';
19 this.http.get(`/api/hello-world`).subscribe(response => this.result = response.text());
20 }
21
22}
We’ve added a function that will be triggered by the button to call the REST endpoint and show the result in the placeholder.
Gradle Build Script
Finally, we are going to modify the build.gradle
script to create frontend integration tasks.
The resulting script looks as follows:
1buildscript {
2 ext {
3 springBootVersion = '1.5.4.RELEASE'
4 }
5 repositories {
6 mavenCentral()
7 }
8 dependencies {
9 classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
10 }
11}
12
13apply plugin: 'java'
14apply plugin: 'eclipse'
15apply plugin: 'org.springframework.boot'
16
17version = '0.0.1-SNAPSHOT'
18sourceCompatibility = 1.8
19
20repositories {
21 mavenCentral()
22}
23
24
25dependencies {
26 compile('org.springframework.boot:spring-boot-starter-web')
27 testCompile('org.springframework.boot:spring-boot-starter-test')
28}
29
30def webappDir = "$projectDir/src/main/webapp"
31sourceSets {
32 main {
33 resources {
34 srcDirs = ["$webappDir/dist", "$projectDir/src/main/resources"]
35 }
36 }
37}
38
39processResources {
40 dependsOn "buildAngular"
41}
42
43task buildAngular(type:Exec) {
44 // installAngular should be run prior to this task
45 dependsOn "installAngular"
46 workingDir "$webappDir"
47 inputs.dir "$webappDir"
48 // Add task to the standard build group
49 group = BasePlugin.BUILD_GROUP
50 // ng doesn't exist as a file in windows -> ng.cmd
51 if (System.getProperty("os.name").toUpperCase().contains("WINDOWS")){
52 commandLine "ng.cmd", "build"
53 } else {
54 commandLine "ng", "build"
55 }
56}
57
58task installAngular(type:Exec) {
59 workingDir "$webappDir"
60 inputs.dir "$webappDir"
61 group = BasePlugin.BUILD_GROUP
62 if (System.getProperty("os.name").toUpperCase().contains("WINDOWS")){
63 commandLine "npm.cmd", "install"
64 } else {
65 commandLine "npm", "install"
66 }
67}
We’ve included a new variable webappDir that points to the Frontend’s application directory.
sourceSets
have been modified to include Angular’s dist directory where the application is packaged and deployed by Angular CLI.
We’ve added a dependency to the standard processResources task to one of our newly generated tasks: buildAngular
.
Two new tasks have been created. buildAngular
will run ng build
command. This task will be added to the standard build tasks and be called after installAngular
task,
It’s important to notice that both these tasks should execute slightly different commands when run on Windows. Neither npm nor ng exist as executables in windows, they are both cmd scripts that can be run in the console just passing their name (without extensions) but are really .cmd
scripts. In order for them to be run as an Exec task, full name of the executable must be provided.
Finally, installAngular
task will run npm install before any other task related to processing resources is executed.
Download sources
You can find a full working project with the sources for this tutorial here: https://github.com/marcnuri-demo/angularboot
To get up and running (it takes a while, npm must download Angular dependencies [not verbose]):
1git clone https://github.com/marcnuri-demo/angularboot.git
2gradle bootRun
Open http://localhost:8080 in your browser
Comments in "Angular + Spring Boot integration using Gradle"
Thanks so much for the great post. I've included a portion of a kotlin-version of the gradle build file for other readers.This is to allow your approach but using kotlin dsl. It should be self-explanatory when compared to your original file. Thanks again!
Best,
Kevin
partial of build.gradle.kts: