Create a single page app with Spring Boot and Vue.js
Tags: springboot, vue, maven
Published: 2019-03-31 07:39:49

---
title: Create a single page app with Spring Boot and Vue.js
published: true
description: How to embed a Vue SPA within a Spring boot app
tags: spring-boot, vue, maven
series: Spring Boot ❤ Vue.js
---

# Objectives

Create a single page app with [Spring Boot](https://spring.io/projects/spring-boot) and [Vue.js](https://vuejs.org/v2/guide/) with:

* One maven build for both frontend and backend.
* Frontend bundled into the Boot app.
* Use Vue router in router history mode, so that we don't have the `#` in the url bar.

# Prerequisites

You'll need to install:

* npm (on macOS, you can simply `brew install npm`)
* [vue-cli](https://cli.vuejs.org/guide/#components-of-the-system) (`npm install -g @vue/cli`
* [JDK](https://jdk.java.net/11/) (This example uses java 11, but any will do, just change the java version when creating the spring project)
* [httpie](https://httpie.org) (optional. You can use https://start.spring.io to bootstrap your Spring project).

# Step by step

## Create a Spring Boot Project

From a terminal

```sh
$ http https://start.spring.io/starter.tgz \
 artifactId==cafe \
 javaVersion==11 \
 language==kotlin \
 name==Cafe \
 dependencies==webflux,devtools,actuator \
 baseDir==cafe | tar -xzvf -
```
This will give you a basic spring boot project under `cafe/`.

Test the build to make sure it works:

```sh
$ ./mvnw test
[INFO] Scanning for projects...
[INFO] 
[INFO] --------------------------< com.example:cafe >--------------------------
[INFO] Building Cafe 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
...
[INFO] 
[INFO] Results:
[INFO] 
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  13.718 s
[INFO] Finished at: 2019-03-30T12:19:24+10:00
[INFO] ------------------------------------------------------------------------

```

## Create a Vue project

Use vue-cli to generate a Hello World Vue CLI project. 

```sh
$ cd src/main
$ vue create frontend \
    --no-git -i '{
  "useConfigFiles": false,
  "plugins": {
    "@vue/cli-plugin-babel": {},
    "@vue/cli-plugin-typescript": {
      "classComponent": true,
      "useTsWithBabel": true
    },
    "@vue/cli-plugin-eslint": {
      "config": "standard",
      "lintOn": [
        "save"
      ]
    }
  },
  "router": true,
  "routerHistoryMode": true,
  "cssPreprocessor": "node-sass"
}'
```

## Configure javascript build output directory

Configure webpack so that compiled static content is under `target`, in keeping with maven conventions. Spring Boot serves static resources from `public` at the classpath root, so we'll take that into consideration as well.    

Edit `src/main/frontend/vue.config.js`:

```json
module.exports = {
	outputDir: '../../../target/frontend/public'
}
```

## Configure the maven build to compile the Vue project

We need to make sure the built static resources end up in the correct place so the maven build and spring know about it

### Configure the npm build

Add this plugin to your `pom.xml`'s `plugins` section:

```xml
<project>
  ...
  <build>
    <plugins>
      ...
      <plugin>
        <groupId>com.github.eirslett</groupId>
        <artifactId>frontend-maven-plugin</artifactId>
        <version>1.7.5</version>
        <executions>
          <execution>
            <id>install node and npm</id>
            <goals>
              <goal>install-node-and-npm</goal>
            </goals>
            <configuration>
              <nodeVersion>v11.12.0</nodeVersion>
            </configuration>
          </execution>
          <execution>
            <id>npm install</id>
            <goals>
              <goal>npm</goal>
            </goals>
            <configuration>
              <arguments>install</arguments>
            </configuration>
          </execution>
          <execution>
            <id>npm build</id>
            <goals>
              <goal>npm</goal>
            </goals>
            <phase>generate-resources</phase>
            <configuration>
              <arguments>run build</arguments>
            </configuration>
          </execution>
        </executions>
        <configuration>
          <workingDirectory>${project.basedir}/src/main/frontend</workingDirectory>
          <installDirectory>${project.build.directory}/node</installDirectory>
        </configuration>
      </plugin>
      ...
    <plugins>
  </build>
</project>
```

Test it out by running `./mvnw process-resources`. You should see the output of the npm build in `target/frontend/`.

### Add compiled static resources to the maven build

Add the generated static component as resource to your build, by adding a `resources` section to your `pom.xml`.

```xml

<project>
  ...
  <build>
    ...
    <resources>
      <resource>
        <directory>${project.build.directory}/frontend</directory>
      </resource>
    </resources>
    ...
  </build>
</project>
```

### Configure spring boot plugin to include static resources

Add this extra `configuration` element to the `spring-boot-maven-plugin`'s config so it will be treated as part of the Spring Boot app.

```xml
<project>
  ...
  <build>
    <plugins>
      ...
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
          <folders>
            <folder>${project.build.directory}/frontend</folder>
          </folders>
        </configuration>
      </plugin>
      ...
    <plugins>
  </build>
</project>
```

Almost there! If you run `./mvnw spring-boot:run` and point your browser to http://localhost:8080/index.html, you should see half of the vue hello world page. We need to do some more work on the backend to things lined up properly.

## Rewrite URLs to make router history mode work

Create a filter that routes everything that's not a bunch of pre-set paths to the static index page.

We will let boot handle the following paths:

* `/actuator`: Spring Boot's actuator has endpoints for health checks, metrics etc
* `/api`: this app's backend API can go under this path
* `/js`, `/css`, `/img`: static resources

```kotlin
package com.example.cafe.web

import org.springframework.stereotype.Component
import org.springframework.web.server.ServerWebExchange
import org.springframework.web.server.WebFilter
import org.springframework.web.server.WebFilterChain
import reactor.core.publisher.Mono

@Component
class VueRoutePathFilter : WebFilter {

    companion object {
        val BOOT_PATHS = listOf(
            "/actuator/",
            "/api/",
            "/js/",
            "/css/",
            "/img/"
        )

        const val SPA_PATH = "/index.html"
    }

    override fun filter(exchange: ServerWebExchange,
                        chain: WebFilterChain): Mono<Void> {
        if (isApiPath(exchange.request.uri.path)) {
            return chain.filter(exchange)
        }

        return chain
            .filter(exchange
                .mutate()
                .request(exchange.request
                    .mutate().path(SPA_PATH)
                    .build())
                .build())
    }

    private fun isApiPath(path: String): Boolean {
        return BOOT_PATHS.any { path.startsWith(it) }
    }
}
```

You should now be able to hit http://localhost:8080 to get the vue Hello World page.

The [sample code](https://github.com/tunaranch/spring-boot-vue-spa/tree/create-hello-world-spa) for this project is on GitHub. Enjoy!