Combining a REST API and JS frontend app with Ratpack and Vue.js

These are some examples of how to integrate a frontend Javascript framework (Vue.js in this case) built with Webpack with a backend JVM framework (Ratpack) built by Gradle. Other than some integration in the build and test phases the backend and frontend apps are decoupled and can be worked on independently. This integration also preserves each framework’s native build system, Webpack for Vue.js and Gradle for Ratpack.

And a quick disclaimer: these are the first Ratpack and Vue.js apps I’ve built, so I’m sure there are things that can be improved.

Raptack intro

Ratpack is lightweight but powerful backend JVM framework that "provides just enough for writing practical, high performance, apps." You can quickly build expressive and readable APIs in either Groovy or Java with Ratpack. And Ratpack provides handy concurrency capabilities that make building concurrent, multi-threaded web applications much easier. If you’re interesting in building Ratpack apps, Dan Woods' new Ratpack book is a phenomenal resource

Vue.js intro

Vue.js is an up-and-coming Javascript frontend framework that I’ve found it to have many of the benefits (component encapsulation, testability, routing, state management, documentation, etc.) but less complexity than some of the larger frameworks. As the Vue.js website describes the framework, it as an "approachable, versatile, and performant" framework. With tools like the Vue command line generator to get up and running quickly and single-file, simple component syntax, Vue is indeed very approachable. It is easy to start being productive in Vue.js very quickly.

Project structure

Now let’s dive into the code. In this example, the Ratpack backend app is using the standard Gradle project structure with source files under src/main and src/ratpack and tests under src/test. We’re free to put the Vue.js frontend anywhere in the project structure, and the src/app folder seems as reasonable a place as any.

I generated the Vue.js initial application using the very handy Vue CLI tool with the Webpack template. Using the CLI tool is a quick way to generate a complete Vue.js app including build and test infrastructure so we aren’t stuck piecing together many libraries just to get started building our frontend application.

Reload changes during development

During development, we can run the Ratpack backend app in development mode to enable hot-reloading of code changes without needing to restart the application server. To do that, run the Ratpack app this Gradle command

gradlew run -t

Now we have the backend running and listening for changes, next up is the frontend. Part of the output of the Vue Webpack template is configuration for the Webpack dev server. Similar to the Gradle task we use for the backend, the Webpack dev server will listen for file changes on the frontend and rebuild the frontend application without needing in a restart. And the Webpack dev server goes one step further - it will push the updates to your browser without even needing to refresh the page, which is a nice time saver.

To run the Webpack dev server, we use this NPM command from inside the src/app folder

npm run dev

The Vue Webpack template also comes with support for a dev proxy that we can use to send all the API calls from the Vue.js app to the Ratpack REST server during development. The Ratpack server is running on localhost:5050, so we’ll edit the proxy table config in config/index.js under src/app to point to our Ratpack backend:

module.exports = {
  dev: {
   ...
    proxyTable: {
      '/api': {
        target: 'http://localhost:5050'
      }
    }
  ...
  }
}

Vue.js build output files into Ratpack

One of the several benefits of using the Vue CLI Webpack template is that in comes with built-in support for integrating the output files into a backend framework. We only have to edit the config/index.js to tell Webpack where to put the generated assets (Javascript, CSS, etc.) and resulting index.html file that points to those assets.

module.exports = {
  build: {
    ...
    index: path.resolve(__dirname, '../../ratpack/static/index.html'),
    assetsRoot: path.resolve(__dirname, '../../ratpack/static'),
    assetsSubDirectory: 'assets',
    assetsPublicPath: '/'
    ...
  }
}

And after generating the output files by running 'npm run build', the src/ratpack/static directory has the index.html and 'assets' folder. Also, we include the .ratpack file to tell Ratpack this is where our static assets are located.

.ratpack
index.html
assets/

Then we configure a file handler in ratpack.groovy to serve up static files from the src/ratpack/static folder

import static ratpack.groovy.Groovy.ratpack

ratpack {
    handlers {
        files {
            dir("static").indexFiles("index.html")
        }
    }
}

Integrating Webpack into the Gradle build

Since the Vue.js frontend is built by Webpack through NPM scripts, we’ll need a way to conveniently integrate these NPM commands into the project’s Gradle build. For that, we’ll use the handy Gradle Node plugin. The Gradle Node plugin will handle downloading and configuring Node for us, so the build won’t rely on the target machine having Node installed and configured.

First, let’s add the Gradle Node plugin to the project’s build.gradle file and configure it. For a full list of config options, check out the plugin’s page.

plugins {
  id "com.moowork.node" version "0.13"
}

node {
  // Version of node to use.
  version = '4.6.0'

  // Version of npm to use.
  npmVersion = '2.15.9'

  // Download node using the versions above.
  download = true

  // Directory for unpacking node
  workDir = file("${project.buildDir}/nodejs")

  // Location of package.json and where node_modules will be stored
  nodeModulesDir = file("${project.projectDir}/src/app")
}

Next, let’s add a task for running the Javascript unit tests and configure it to run as part of the check task. If we using NPM directly the command to run the tests would be npm run test, so let’s use the Node plugin’s NpmTask type with the arguments run and test. Also, let’s set the inputs and outputs of the task so Gradle’s up-to-date checking can only run the task when it’s needed.

task testJs(type: NpmTask, dependsOn: 'npmInstall') {
  inputs.dir('src/app/test/unit')
  outputs.dir('src/app/test/reports')

  args = ['run', 'test']
}
check.dependsOn('testJs')

And a Gradle task for generating the Webpack output files, which needs to run the NPM command npm run build. And just like the testJs task, we’ll configure inputs and outputs for the assembleJs task so Gradle’s up-to-date checking can figure out when the task needs to be run.

task assembleJs(type: NpmTask, dependsOn: 'npmInstall') {
  inputs.dir('src/app')
  outputs.dir('src/ratpack/static/assets')
  outputs.file('src/ratpack/static/index.html')
  args = ['run', 'build']
}

Testing

For full, end-to-end testing that the frontend and backend work well together, we’ll use the Geb browser functional testing library and the Spock testing framework. Ratpack provides an easy way to start up the application inside our functional test by just instantiating a GroovyRatpackMainApplicationUnderTest object. Then the test can use Geb to drive the web browser and test the running application.

package standup.geb

import geb.spock.GebReportingSpec
import ratpack.groovy.test.GroovyRatpackMainApplicationUnderTest
import spock.lang.AutoCleanup

class StandupGebSpec extends GebReportingSpec {
    @AutoCleanup
    def aut = new GroovyRatpackMainApplicationUnderTest()

    def setup() {
        URI base = aut.address
        browser.baseUrl = base.toString()
    }

    void "should submit status"() {
        given:
        HomePage homePage = to HomePage

        String name = 'Craig Atkinson'
        String yesterday = 'Completed that important task'
        String today = 'Investigating production issue'

        when:
        homePage.submitStatus(name, yesterday, today)

        then:
        waitFor { homePage.numberOfStatusDisplays == 1 }

        and:
        StatusDisplayModule myStatus = homePage.findStatusFor(name)
        assert myStatus?.yesterday == yesterday
        assert myStatus.today == today
    }
}

Prior to running the browser functional tests, the Gradle build needs to generate the Webpack output files for the frontend that get served up from the Ratpack side. And since only the browser functional tests rely on the frontend build, let’s split the browser tests into their own Gradle task that depends on the Javascript build output.

task testBrowser(type: Test, dependsOn: 'assembleJs') {
  include '**/*GebSpec*'

  // Pass system properties through to the testBrowser task so we can pass in the 'geb.env' property to run tests
  // in different browsers. Adapted from http://mrhaki.blogspot.com/2015/09/grails-goodness-passing-system.html
  systemProperties System.properties
}
check.dependsOn('testBrowser')

After creating the testJs and testBrowsr tasks, we updated the check task to depend on them, so now we can run all the different types of tests in the project with a single Gradle command gradlew check

Full Code

This post tries to highlight some of the key pieces of integrating a Webpack-built frontend app with a Gradle-built backend app. I left out much of the app code itself, but the full code with everything included is on Github at https://github.com/craigatk/ratpack-standup

Whew, this turned into a long post. If you made it this far, I hope the info proves helpful!