The Vue.js is the JavaScript framework currently on everyone’s lips. This is no coincidence of course, as it offers a quick start into the world of modern, component-based web frameworks. Vue.js has already earned its own place among the big players, offering an alternative to popular options such as Angular und React [1]. An easier learning curve and higher development speed compared to the well-known representatives makes Vue.js a favorite for many projects [2].
Yet in practice, the question rises as to how the new star in the JavaScript sky can be integrated into existing stacks – especially with the popular Spring Boot Java microservices framework. In addition, a reasonable (local) process must be explored so that solutions can be developed quickly and easily. Last but not least, both frameworks should integrate seamlessly into existing continuous integration and delivery pipelines should be automatically deployed.
STAY TUNED!
Learn more about JAX London
Project setup
To give the reader a fully knowledgeable view of each topic, GitHub provides a complete example project [3]. It can also be used as a template for your own first projects based on Spring Boot and Vue.js. As a developer, the project setup is usually the first thing you have contact with in the framework. A clear structure here can make it easier to get started. The example project depicts a familiar Maven structure (Fig. 1).
The pom.xml [4] in the main folder of the sample application spring-boot-vuejs essentially contains both Maven modules – backend and frontend:
<modules>
<module>frontend</module>
<module>backend</module>
</modules>
The build order of the two modules is important here and should not be changed. First, the frontend module is built, followed by the backend module.
Vue.js frontend
To fully understand the next steps, a functioning Node.js installation [5] is required. This can be done quickly with the help of a package manager. For example, a brew install node installs everything you need on a Mac. In addition, the Vue.js command line interface (CLI) vue-cli (Fig. 2) is needed, which can be easily installed using the Node.js package manager npm [6]: npm install –global vue-cli. Now you can create the body of a Vue.js project in the frontend directory: vue init webpack frontend. The command is followed by a series of questions that need to be answered. The generated body project has everything you need ready, so you can start with your first Vue.js experiments.
If you tend to consider the world of web frameworks from a measured offset, the development speed prevailing here can already make you a bit queasy. Concepts like npm Builds [6], ESLint [7], webpack [8] and the like can sound like rocket science at first. And often, it can feel like rocket science too.
But there is relief, especially if you come from the ranks of backend developers. Because in that case, Maven [9] is one of the tools you use daily. The frontend Maven plug-in [10] helps you keep the entire jungle of web frontend build tools under control. All you need for this can, for example, be found in the configuration of pom.xml in the frontend directory of the sample project [11] (Listing 1).
Listing 1: “pom.xml”
<build> <plugins> <plugin> <groupId>com.github.eirslett</groupId> <artifactId>frontend-maven-plugin</artifactId> <version>${frontend-maven-plugin.version}</version> <executions> <execution> <id>install node and npm</id> <goals> <goal>install-node-and-npm</goal> </goals> <configuration> <nodeVersion>v9.11.1</nodeVersion> </configuration> </execution> <execution> <id>npm install</id> <goals> <goal>npm</goal> </goals> <configuration> <arguments>install</arguments> </configuration> </execution> <execution> <id>npm run build</id> <goals> <goal>npm</goal> </goals> <configuration> <arguments>run build</arguments> </configuration> </execution> </executions> </plugin> </plugins> </build>
The configuration of frontend Maven plugin is divided into three sections. First, the Maven Goal install-node-and-npm makes sure that the correct version of Node.js is installed. If the version of Node.js is higher than the (discontinued) 4.0.0, the frontend Maven plugin will install the corresponding npm version as well.
The second Maven Goal npm with the optional install argument takes care of installing all dependencies needed in the Vue.js frontend. Similar in principle to a pom.xml, these dependencies are described in the package.json [12] – including the explicit specification of all versions used. The package.json initially created by the vue init webpack frontend command already contains a rather extensive list of dependencies. If you install additional dependencies using the npm install dependency-name command, the package.json automatically expands.
Finally, in the third and final Maven Goal npm with run build arguments, the full frontend build process is executed. All three steps are transparent to the developer. Basically, only Maven know-how is needed to get started on using the project. As your experience level increases, the full frontend build process can be approached step-by-step, and any part can be customized to meet project requirements.
Spring Boot backend
One of the easiest ways to create a new Spring Boot application is to use the https://start.spring.io/ page. Here you assign typical Maven names for Group and Artifact, select the Web dependency and click Generate Project. The generated .zip file can then be easily unpacked into the backend directory. Only two things need to be adjusted here for integration with Vue.js: First, the spring-boot-starter-parent moves into the pom.xml [4] of the main directory, because it is the parent POM for the backend module.
The second item is in a class of its own. This is because the maven-resources-plugin [13] is used to copy the HTML, JavaScript and CSS files later generated from the Vue.js code from the frontend to the backend module. Therefore, the integrated web server of Spring Boot can also immediately deliver the entire frontend, in addition to the usual (REST) backend interfaces. A section of the pom.xml of the backend module [14] (Listing 2) shows specifically how this works.
Listing 2: “Pom.xml” of the “backend” module
<plugin> <artifactId>maven-resources-plugin</artifactId> <executions> <execution> <id>Copy Vue.js frontend assets</id> <phase>generate-resources</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <outputDirectory>src/main/resources/public</outputDirectory> <overwrite>true</overwrite> <resources> <resource> <directory>${project.parent.basedir}/frontend/target/dist</directory> <includes> <include>static/</include> <include>index.html</include> </includes> </resource> </resources> </configuration> </execution> </executions> </plugin>
Here you have the answer to the build order of the frontend and backend Maven modules. Of course, because the data can be copied, it must be there. The maven-resources-plugin [13] simply copies the results of the frontend build process from /frontend/target/dist to /backend/src/main/resources/public. With the help of a simple command like java -jar backend-0.0.1-SNAPSHOT.jar, not only can the whole Spring Boot application start as usual, but the complete frontend can also be delivered under the same port.
Create a simple RESTful Web service to prepare for future application cases. The BackendController.java [15] class in the sample project offers an extremely simple resource called /api/hello; its calling is tested within the BackendControllerTest.java [16] test class. The REST Assured [17] tool in (Listing 3) can, for example, be used for this.
Listing 3: REST Assured
@RunWith(SpringRunner.class) @SpringBootTest( classes = SpringBootVuejsApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT ) public class BackendControllerTest { @LocalServerPort private int port; @Test public void saysHello() { when() .get("http://localhost:" + port + "/api/hello") .then() .statusCode(HttpStatus.SC_OK) .assertThat() .body(is(equalTo(BackendController.HELLO_TEXT))); } }
The observant reader has probably already noticed: The maven-resources-plugin [13] copies the frontend files from a directory (where webpack normally does not write anything) – specifically to frontend/target. But, since we are dealing with a standard Maven project, webpack should also be nicely configured. The frontend / config / index.js [18] file can be adapted for this purpose. The lines
// Template for index.html
index: path.resolve(__dirname, ‚../dist/index.html‘),
// Paths
assetsRoot: path.resolve(__dirname, ‚../dist‘),
können einfach durch die folgenden ausgetauscht werden:
// Template for index.html
index: path.resolve(__dirname, ‚../target/dist/index.html‘),
// Paths
assetsRoot: path.resolve(__dirname, ‚../target/dist‘),
The generated HTML, JavaScript, and CSS files already end up in the frontend/target/dist directory and are therefore also available to be accessed by the maven-resources-plugin in the pom.xml of the backend module [14].
And off we go!
That’s all. Basically, the entire project setup is now ready with Spring Boot and Vue.js. If you now simply execute the Maven build in the main directory of the application [3] via mvn clean install, it should run without error. Then, as is usually the case with a Spring Boot application, you can start everything with this command: java -jar backend/target/backend-0.0.1-SNAPSHOT.jar. The browser can now be opened and the first executable version of the application can be viewed under http://localhost:8088 (Fig. 3).
If you use a newly created project instead of the sample project, the port may need to be adjusted; otherwise you may not be able load the page. In the sample project, the port is configured to 8088 in the backend/src/main/resources/application.properties [19] file.
The development process with Spring Boot and Vue.js
After the first hurdles have been overcome and the application is running, it may be worthwhile to look at the development process with Spring Boot and Vue.js. Of course, the Maven build process works in an exemplary fashion, so there should be no obstacles that would block later deployment. However, the development process would be very tough. Each time, you would have to run a complete Maven build including integrated frontend build and also boot up the Spring Boot application. This entire process would have to be done even with the smallest code change in the frontend.
Even though these processes are fully automated, the goal should be to achieve a much faster roundtrip during development. Fortunately, webpack offers the so-called webpack-dev-server in [20] for this purpose. This tool is quite popular. It makes sure that an update of the frontend sources is immediately passed through the entire frontend build process in the background and becomes visible directly in the browser. The created project setup already has everything that is needed preconfigured. Only the following command needs to be run in the frontend directory: npm run dev. This starts up the preconfigured webpack-dev-server and opens the browser. For this step, it is worthwhile to change one small thing in the sample project in the Frontend Sources [21] and have the command executed on its own.
In addition to the webpack-dev-server other tools can also make a developer’s life easier. The Vue.js browser extension [22] for developer tools in Chrome [23] or Firefox [24] can be especially useful here. For example, the Vue.js browser extension makes work with the Vue.js components easier (Fig. 4).
In order to make quicker use of code changes in the Spring Boot backend development process, the spring-boot-devtools [25] module can also be used. For this purpose, it needs to be integrated in the pom.xml of the backend module [14]:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
If the Spring Boot backend is now started in the IDE (by clicking Run in the SpringBootVuejsApplication.java [26] main class) code changes will lead to a hot reload of the backend. With the help of the tools presented here, development with Spring Boot and Vue.js should now go much faster.
Using Spring RESTful Web Services in Vue.js
If Spring Boot and Vue.js are integrated, data exchange between the two frameworks must be able to take place. RESTful Web Services, which are usually provided by the Spring Boot backend, are a good choice. In the example project on GitHub, a /api/hello resource has already been provided. The corresponding class BackendController.java [15] and matching test class BackendControllerTest.java [16] have been created. But how can RESTful Web Services be called up from the Vue.js frontend? Just like for the build tools, there are countless frameworks available for this task. A currently popular representative is the axios [27] framework, which provides an HTTP client based on the Promise API. It has already collected some GitHub stars, and not without reason. To use axios in the project, all you need to do is install the corresponding npm dependency. You can do this in the frontend directory using the npm install axios –save command. Now you can use axios in the project (Listing 4). Learning to use it is quick and easy. In any Vue.js component, such as the Service.vue [28] in the sample project, the code shown in Listing 4 will be supplemented in the <script> tag.
Listing 4: axios
import axios from ‘axios’
data () { return { response: [], errors: [] } }, callRestService () { axios.get(`/api/hello`) .then(response => { this.response = response.data }) .catch(e => { this.errors.push(e) }) }}
Here, the axios library is provided by applying the import instruction. This is followed by the definition of required data structures such as response and errors. The actual call of the RESTful Web service of the Spring Boot backend takes place in the callRestService () method. For this purpose, a resource is controlled via /api/hello based on implicit host and port information. In the then() method provided by axios, the body of the response is written to the previously created response array, which occurs asynchronously.
The last step in calling the RESTful Web service is to call the callRestService() method somewhere in the code of the Vue.js template. In the sample application, this is a button in the Service.vue [28] file, which is named accordingly:
<button class=”Search__button” @click=“callRestService()“>CALL Spring Boot REST backend service</button>
<h3>{{ response }}</h3>
For the sake of simplicity, the data of the HTTP response body will be written directly to the <h3>- tag and displayed to the user. If the sample application is running, this can be easily seen by simply calling up the http://localhost:8088/#/callservice/ URL in your browser. A click on the CALL Spring Boot REST backend service button will then display the string Hello from Spring Boot Backend! coded in the BackendController.class [15] of the Spring Boot backend in the browser (Fig. 5).
Avoid the Same-Origin-Policy hell
Calling up the Spring Boot backend RESTful Web Services works just fine, except during the local development process, which uses the webpack-dev-server to provide fast feedback. This is because the webpack-dev-server started by npm run dev is configured to provide the frontend at http//localhost:8080. However, the Spring Boot backend launched in the IDE offers the RESTful Web Services at http:/localhost:8088. Here, a core concept of the Web security architecture strikes back with the following: The Same Origin Policy (SOP) [29] prevents access with a corresponding error message.
There is also a remedy here: The Cross-Origin Resource Sharing Protocol(CORS) [30] was designed specifically for this application case and provides access with exception rules. CORS support can be configured accordingly in axios [31] as well as in Spring Boot [32]. Yet, that entails some code extensions, which then additionally appear in the productive code even though they are not needed there. Fortunately, the webpack-dev-server with its proxying feature helps again here to avoid complex CORS configuration. All you need to do is configure a proxy for all webpack-dev-server requests. This can be implemented in the file frontend/config/index.js [18] in the dev.proxyTable field (Listing 5).
Listing 5: Configure proxy for webpack-dev-server requests
dev: { ... proxyTable: { // proxy all webpack dev-server requests starting with /api to our Spring Boot backend (localhost:8088) '/api': { target: 'http://localhost:8088', changeOrigin: true } },
With this configuration, the webpack-dev-server uses the http-proxy-middleware [33] to redirect any frontend requests with the /api prefix from http://localhost:8080 to http://localhost:8088. The origin HTTP header will be adjusted accordingly, so that no code change in the Vue.js frontend or Spring Boot backend is necessary. Since the RESTful Web Services of the Spring Boot backend [15] are all configured at the class level for the source resource /api, this will apply to all web services calls of this class:
@RestController()
@RequestMapping(„/api“)
public class BackendController {
@RequestMapping(path = „/hello“)
public @ResponseBody String sayHello() {
return „Hello from Spring Boot Backend!“;
}
Integration with continuous integration and delivery pipelines
In the course of this article, many aspects of the integration of Spring Boot with Vue.js have been highlighted. Initially, the question was how both can be reasonably integrated into existing continuous integration and delivery pipelines. Here it is necessary to have an exemplary continuous integration and delivery pipeline to begin with. This should no longer be a major problem and has also been implemented quite quickly in the environment of open source projects. The sample project on GitHub [3] relies on Travis CI [34] as a continuous integration platform and Heroku [35] for the deployment and provision of appropriate infrastructure. The sample application is also accessible at this URL: https://spring-boot-vuejs.herokuapp.com. Travis CI is configured using a YAML-based file named .travis.yml [36], which is extremely compact:
language: java
jdk:
– oraclejdk8
script: mvn clean install jacoco:report coveralls:report
Each Git push in the master branch of the project repository on GitHub triggers a Maven build, which immediately builds the entire application including the Vue.js frontend and Spring Boot backend, thanks to the frontend maven plugin, and provides an executable jar file at the end. Then reports on test coverage and code coverage are generated using JaCoCo [37] and Coveralls [38].
As is common with similar pipelines, deployment to Heroku will only be triggered once all these steps have been successfully completed. This is easy to implement using Heroku’s Automatic Deploy feature. The configuration view provides a checkbox called Wait for CI to pass before deploy, in addition to its actual function of deploying the sample application. This triggers deployment only when all the steps configured in Travis CI give the go-ahead (Fig. 6).
Fig. 6: Heroku deploys the full sample project after each Git Push
The only requirement for this is the connection of Heroku with the GitHub repository, which is then also displayed under Deployment Method. Every push with reasonable content triggers deployment on Heroku, proving the usability of Spring Boot and Vue.js in continuous integration and delivery pipelines using this project setup.
Conclusion
We have seen how the two stars in the Java or JavaScript sky can be optimally integrated. No extraordinary know-how is required, and even as a JavaScript beginner you can quickly find your way in the simple and clearly structured project setup. By using Maven as a well-known build tool, integration into existing continuous integration and delivery pipelines is done quickly. In addition, it has been shown how the webpack-dev-server can be used to guarantee fast local development roundtrip. The proper configuration of a proxy saves you from frustration due to SOP violations when calling the RESTful Web Services of the Spring Boot backend. Thus, a developer’s life is no longer threatened by hardship due to unclear framework conditions, and you can fully devote your time to the implementation of professional features in the project. Have lots of fun with it!
Links & literature
[1] Motroc, Gabriela: „Der neue Stern am JavaScript-Himmel: Vue.js gewinnt den Beliebtheitswettbewerb“: https://entwickler.de/online/javascript/javascript-vue-579827096.html
[2] Elizondo, Luis: „Why we moved from Angular 2 to Vue.js (and why we didn’t choose React)“: https://medium.com/reverdev/why-we-moved-from-angular-2-to-vue-js-and-why-we-didnt-choose-react-ef807d9f4163
[3] Sample project: https://github.com/jonashackt/spring-boot-vuejs/
[4] pom.xml: https://github.com/jonashackt/spring-boot-vuejs/blob/master/pom.xml/
[5] Node.js: https://nodejs.org/en/
[6] npm: https://www.npmjs.com
[7] ESLint: https://eslint.org
[8] webpack: https://webpack.js.org
[9] Maven: https://maven.apache.org
[10] frontend-maven-plugin: https://github.com/eirslett/frontend-maven-plugin/
[11] Frontend directory of the sample project: https://github.com/jonashackt/spring-boot-vuejs/blob/master/frontend/pom.xml/
[12] package.json: https://github.com/jonashackt/spring-boot-vuejs/blob/master/frontend/package.json/
[13] Apache Maven Resources Plugin: https://maven.apache.org/plugins/maven-resources-plugin/
[14] Section of the pom.xml of the backend module: https://github.com/jonashackt/spring-boot-vuejs/blob/master/backend/pom.xml/
[15] BackendController.java class in the sample project: https://github.com/jonashackt/spring-boot-vuejs/blob/master/backend/src/main/java/en/jonashackt/springbootvuejs/controller/BackendController.java/
[16] Test class BackendControllerTest.java: https://github.com/jonashackt/spring-boot-vuejs/blob/master/backend/src/test/java/en/jonashackt/springbootvuejs/controller/BackendControllerTest.java/
[17] REST Assured: http://rest-assured.io/
[18] Frontend/config/index.js file: https://github.com/jonashackt/spring-boot-vuejs/blob/master/frontend/config/index.js/
[19] Sample project: https://github.com/jonashackt/spring-boot-vuejs/blob/master/backend/src/main/resources/application.properties/
[20] webpack-dev-server: https://webpack.js.org/configuration/dev-server/
[21] Frontend Sources: https://github.com/jonashackt/spring-boot-vuejs/tree/master/frontend/src/
[22] Vue.js-Browsererweiterung: https://github.com/vuejs/vue-devtools/
[23] Vue.js devtools for Chrome: https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd
[24] Vue.js devtools for Firefox: https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/
[25] Spring Boot Developer Tools: https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-devtools.html
[26] Main class SpringBootVuejsApplication.java: https://github.com/jonashackt/spring-boot-vuejs/blob/master/backend/src/main/java/en/jonashackt/springbootvuejs/SpringBootVuejsApplication.java/
[27] axios: https://github.com/axios/axios/
[28] Service.vue: https://github.com/jonashackt/spring-boot-vuejs/blob/master/frontend/src/components/Service.vue/
[29] Same Origin Policy: https://en.wikipedia.org/wiki/Same-origin_policy
[30] Cross-Origin Resource Sharing: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS/
[31] CORS Support in axios: https://github.com/jonashackt/spring-boot-vuejs#enabling-axios-cors-support
[32] CORS Support in Spring Boot: https://github.com/jonashackt/spring-boot-vuejs#enabling-spring-boot-cors-support
[33] http-proxy-middleware: https://github.com/chimurai/http-proxy-middleware/
[34] Travis CI: https://travis-ci.org/jonashackt/spring-boot-vuejs/
[35] Heroku: https://www.heroku.com
[36] travis.yml: https://github.com/jonashackt/spring-boot-vuejs/blob/master/.travis.yml
[37] JaCoCo: https://github.com/jacoco/jacoco/
[38] Coveralls: https://coveralls.io/github/jonashackt/spring-boot-vuejs?branch=master