Thinking about project structure when hosting Vue.js on Spring WebFlux

Six months after Spring Boot 2.0 was released on March 1, 2018 [ref 1], paradigm shifts such as official support for Kotlin and reactive web programming with Spring WebFlux are slowly becoming more prevalent It’s that time of year again. And it has become a common pattern to develop web apps and systems by combining these back-end technologies with the front-end technology of the ever-evolving JavaScript framework. On the other hand, there are approaches and frameworks that straddle the boundaries of this combination, which can be a great fit, depending on the composition of the development team.

By the way, there are three main factions that have survived in the front-end world: Angular, React, and Vue.js [ref2], and more recently we’ve seen some up-and-coming ones like Mithril. Many frameworks based on each of these frameworks have appeared [ref 3], such as Next.js (based on React) and Nuxt.js (based on Vue.js) for SSR (Server Side Rendering), which are well-known examples. I’m going to use WebFlux for DB operations, etc. and WebFlux to serve the results of Vue.js build.

In this article, I’ll use WebFlux for DB operations, Vue.js for SPA (Single Page Application) screens, and WebFlux to serve the results of Vue.js builds (and of course, it can be applied to other frameworks as well). There are three advantages to this configuration:

  1. front-end and back-end code can be managed in one place
  2. both front-end and back-end developers can easily run combined tests Can be deployed as a single application (a single jar file). 3.

The downside is that it won’t work for you if you want to deploy them separately in the first place (you want to scale the front serve and the backend separately), and the directory structure is a bit more complicated.

Basic Strategy.

Suppose Spring WebFlux builds Gradle and Vue.js with npm, and wants to develop without breaking any conventions of directory layout on both sides. The problem is the handling of src: Gradle and npm have completely different src directory structures, so it’s wise to keep them separate. Of course, the src renaming can solve this problem, but let’s make the most of Gradle’s subprojects feature. The conceptual structure would be something like this:

``console app ├──── backend │ Gradle subprojects │└ └ └ └ │└└└└└└└└ The ├──── gradle root project ├──── npm file └ └ ── src └ └ └ └ vue.js Code

We'll assign the following roles to each

- npm: Build Vue.js
- The gradle root project: calling the npm build using the gradle-node-plugin
- gradle subproject: Building WebFlux

### Creating a Project.

We need the following three things:

- The `npm` command (which comes with Node.js)
- The `vue` command (`npm install -g @vue/cli`)
- The `gradle` command (easy to install on Homebrew and Chocolatey)

:warning: The npm package for the Vue CLI is `vue-cli` up to version 2 and `@vue/cli` from version 3 onwards <sup>[ref 4]</sup>. This time, we're using the 3 basis.

This time it's explained in base 3.
$ vue create webflux-vuejs-demo

:bulb: This command creates a directory with a given name and creates a project file in it.

When you run it, you will be asked for the following

When you run it, you will be asked for the following: ``console Vue CLI v3.0.0-rc.8 ? Please pick a preset: default (babel, eslint) Manually select features

You can switch between :arrow_up: and :arrow_down: on the keyboard. Leave it at default and turn and type Enter.
You may also get "Your connection to the default npm registry seems to be slow." and recommend another npm repository. In my case, it's and...Chinese.

When it's done, you'll be prompted to try the next command. Let's try it out.

$ cd webflux-vuejs-demo
$ npm run serve

If you go to http://localhost:8080/ in your browser…

Congratulations on the display! And don’t be complacent. Here’s the story…

In the same directory where you hit npm earlier, you can run the following command: ````console.

``console $ gradle init wrapper

The `build.gradle` and a few other files have been generated, and we've added the following to the `.gitignore` file Add the following to the `.gitignore` file: `console`.

The following is added to the `.gitignore` file: `console
# Gradle

Edit the build.gradle. Edit the build.gradle; we don’t need the original comment lines, so let’s get rid of them.

Edit the groovy.gradle to remove the comment line. plugins { id “com.moowork.node” version “1.2.0” }

node { version = ‘10.7.0 download = true }

Now we can do the same thing with npm from gradle.

| npm | gradle
| ------------------- | ----------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| /gradlew npm_install | . /gradlew npm_install | .
| /gradlew npm run lint | . /gradlew npm_run_lint | .
| /gradlew npm_run_build | . /gradlew npm_run_build | .
| /gradlew npm_run serve | . /gradlew npm_run_serve | .

Additionally, you can call your favorite tasks by defining them separately as follows, like `. /gradlew runBuild` by defining them separately as follows.

. /gradlew runBuild`.
task runBuild(type: NpmTask) {
    args = ['run', 'build']

:bulb: . /gradlew tasks --all to see the list of tasks and rules you can beat.

Configure WebFlux.

Once you’ve experienced the beauty of the gradle-node-plugin (com.moowork.node), it’s time to configure WebFlux.

Following the basic strategy above, we will create a backend subproject.

The backend is a subproject that follows the basic strategy of creating a subproject. $ mkdir backend $ vi backend/build.gradle


plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.2.51'
    id 'org.jetbrains.kotlin.plugin.spring' version '1.2.51'
    id 'org.springframework.boot' version '2.0.3.
    id 'io.spring.dependency-management' version '1.0.6.

repositories {

dependencies {
    compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
    compile 'org.jetbrains.kotlin:kotlin-reflect'
    compile 'org.springframework.boot:spring-boot-starter-webflux'
    testCompile 'org.springframework.boot:spring-boot-starter-test'

compileKotlin {
    kotlinOptions {
        jvmTarget = '1.8'
compileTestKotlin {
    kotlinOptions {
        jvmTarget = '1.8'

bootJar {
    baseName = 'webflux-vuejs-demo'
    archiveName = baseName + '.' + '.'

Then add the following to the settings.gradle in your project root.

And add the following to the settings.gradle in the project root: settings.gradle. include ‘backend’

Implementing the Router Function, written in Kotlin.

I wrote it in Kotlin: `backend/src/main/kotlin/com/github/mikan/demo/App.kt`:

package com.github.mikan.demo

import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.context.annotation.Bean
import org.springframework.http.
import org.springframework.stereotype.
import org.springframework.web.reactive.function.server.HandlerFunction
import org.springframework.web.reactive.function.server.RequestPredicates
import org.springframework.web.reactive.function.server.RouterFunction
import org.springframework.web.reactive.function.server.RouterFunctions
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse
import reactor.core.publisher.

class App

fun main(args: Array<String>) {, *args)

class IndexHandler {
    private lateinit var indexHtml: Resource

    fun indexRoutes(): RouterFunction<ServerResponse> {
        return RouterFunctions.route(RequestPredicates.GET("/"), HandlerFunction { get(it) })

    fun get(request: ServerRequest): Mono<ServerResponse> {
        return ServerResponse.ok().contentType(MediaType.TEXT_HTML).syncBody(indexHtml)

:bulb: Spring Boot’s Welcome Page feature originally maps /static/index.html to / automatically, but WebFlux doesn’t do that [ref 5]. So it is manually routed as a Resource workaround as shown in the reference. This may be unnecessary in future updates.

Vue.js build artifacts integration.

As mentioned earlier in Kotlin’s code classpath:/static/index.html, you need to pass the Vue.js build artifacts to backend to make this work. However, it’s easy to do in practice. First, we need to create a directory.

The first thing we need to do is create a directory called ``console $ mkdir -p backend/src/main/resources/static

Add the following to your `.gitignore`.

The following is added to the ``.gitignore``.
# Frontend output for backend

Modify the build in package.json as follows

-     "build": "vue-cli-service build",
+     "build": "vue-cli-service build --dest backend/src/main/resources/static",

Now you’re ready to go. Now it’s time to build and run it!

$ . /gradlew npm_run_build
$ . /gradlew bootRun

The result is the same. The result is the same, but it’s working with WebFlux. Now we need to develop the front end and back end.

For your reference, I’ve published a sample of this project on GitHub. Please take a look at it.

Using Vue Router’s history mode

Vue.js comes with a nice routing mechanism, Vue Router, which has two modes of operation: hash and history [Ref 6]. The hash mode simulates a complete URL by including /#/ in the URL, but as a way to remove this, a history mode is provided using HTML5’s History API feature. In this case, WebFlux serves the index.html on the first try, after which the page transition is accomplished in the JavaScript world (it doesn’t fetch it to WebFlux page by page).

To avoid 404 on the first pass, you need to drop every possible path to index.html. A simple solution is to define a pattern with a path parameter (which we don’t use).

The ``console @Bean fun indexRoutes(): RouterFunction { val handler = HandlerFunction { get(it) } return RouterFunctions.route(RequestPredicates.GET("/"), handler) .andRoute(RequestPredicates.GET("/page”), handler) .andRoute(RequestPredicates.GET("/page/{sub1}"), handler) .andRoute(RequestPredicates.GET("/page/{sub1}/{sub2}"), handler) }

WebFlux also provides other static resources (js, css, etc.), so you can't make the top level a path parameter (in the example `page`). And considering other API endpoints and so on, you need to design your URLs so that they do not conflict with each other.

### Using Quasar Framework

One of the Vue.js based frameworks is Quasar Framework <sup>[ref 7]</sup>.
When developing here, the commands used to create a project and the way to set up the integration of the previous build artifacts will change.
Here's a step-by-step explanation of how to create it.

What you need additionally:

- `quasar` command (`npm install -g quasar-cli`).
- The `vue init` command (`npm install -g @vue/cli-init`)

Procedure :

$ quasar init webflux-quasar-demo Project name (internal usage for dev) webflux-quasar-demo Project name (internal usage for dev) webflux-quasar-demo Project product name (official name) Project product name (official name) Quasar App Project description A Quasar Framework app Project description A Quasar Framework app ? Author xxx Check the features needed for your project: ESLint Check the features needed for your project: ESLint Pick an ESLint preset standard Pick an ESLint preset Standard ? Cordova id (disregard if not building mobile apps) Should we run npm install for you after the project has been created? Should we run npm install for you after the project has been created?

To keep the process consistent, we've changed the last question to `NPM`. Otherwise, we've left the default to enter.

The structure of the Gradle project is the same as before, but the location of the output of the Gradle project has been changed from `package.json` to `quasar.conf.js` and the following path is added to the `build` section as `distDir` Diff.

      build: { 'backend/src/main/resources/static'
+ distDir: 'backend/src/main/resources/static',
       scopeHoisting: true,
       vueRouterMode: 'history',

Then, as usual, add the build script to your package.json to build with . /gradlew npm_run_build to build with gradlew npm_run_build, and add a build script to the package.json.

The `diff “scripts”: { “build”: “quasar build

  • “build”: “quasar build”, “lint”: “eslint –ext .js,.vue src”, “test”: “echo “No test specified" && exit 0” },

This will serve WebFlux with a `. /gradlew npm_run_build bootRun` to serve WebFlux.

One more thing. The `quasar init` command generates various files, among them `.editorconfig`, but it forces a two-space indentation on all the files, which makes the standard Kotlin four-space file terrible. However, this forces all files to have a two-space indentation, which makes Kotlin, where four spaces is the norm, look terrible. So we're going to fix it as follows ``diff - [*] + [*. {js,vue}] charset = utf-8 indent_style = space indent_size = 2

IntelliJ supports .editorconfig by default, so if you forget it exists, you’ll be confused as to why it’s two spaces :scream:

For your reference, I’ve also made a sample of Quasar version available on GitHub. For your reference, I’ve also made a sample of Quasar version available on GitHub.

Happy hacking!


  1. Spring Boot 2.0 goes GA
  2. kamranahmedse/developer-roadmap: Roadmap to becoming a web developer in 2018
  3. 25+ Best Vue.js Frameworks " CSS Author
  4. Overview | Vue CLI 3
  5. [Welcome page(classpath:/static/index.html) not being resolved for / uri in webflux project - Issue #9785 - spring-projects/spring-boot](https ://
  6. HTML5 History Mode | Vue Router
  7. Quasar Framework