Show Navigation

Combining the React profile projects

Learn how to generate a JAR file which combines React and Grails production artefacts.

Authors: Zachary Klein

Grails Version: 3.3.0

1 Grails Training

Grails Training - Developed and delivered by the folks who created and actively maintain the Grails framework!.

2 Getting Started

In this guide, you are going to create a combined build of the server and client projects from the React profile. By default, the React profile sets up separate frontend and backend apps, which can make for a more efficient development process. However, this may not fit your deployment needs, particularly if your entire app is being deployed to a servlet container such as Tomcat, or being executed as a "fat" JAR. Thanks to the flexibility of Grails and the Gradle build tool, we can configure a unified build of our Grails/React app with only a few changes.

2.1 What you will need

To complete this guide, you will need the following:

  • Some time on your hands

  • A decent text editor or IDE

  • JDK 1.7 or greater installed with JAVA_HOME configured appropriately

2.2 How to complete the guide

To get started do the following:

or

The Grails guides repositories contain two folders:

  • initial Initial project. Often a simple Grails app with some additional code to give you a head-start.

  • complete A completed example. It is the result of working through the steps presented by the guide and applying those changes to the initial folder.

To complete the guide, go to the initial folder

  • cd into grails-guides/react-combined/initial

and follow the instructions in the next sections.

You can go right to the completed example if you cd into grails-guides/react-combined/complete

3 Writing the Application

If you open the initial project of this guide, you will find a plain project generated from the React profile.

We did one minor change. We moved the initial/server/gradle.properties to the root directory initial/gradle.properties

Both the server Grails app and the client React app have production build tasks set up - you can run ./gradlew server:assemble to build a WAR or JAR file, and you can run yarn build (or ./gradlew client:build) to generate a static React/webpack bundle. Our first step to create a new build.gradle file at the top of our project structure, above the client and server sub-projects.

3.1 Configuring Gradle

Create a new build.gradle file (at the same level as settings.gradle), and add the following content:

build.gradle
task copyClientResources(dependsOn: ':client:build') { (1)
    group = 'build'
    description = 'Copy client resources into server'
    doLast {
        copy {
            from project(':client').buildDir.absolutePath
            into "${project(':server').buildDir}/resources/main/public"
        }
    }
}
task assembleServerAndClient(dependsOn: ['copyClientResources', ':server:assemble']) {  (2)
    group = 'build'
    description = 'Build combined server & client JAR'

    doLast {
        copy {
            from fileTree(dir: "${project(':server').buildDir}/libs/") (4)
            into "$rootDir/build/"
        }

        logger.quiet "JAR generated at $rootDir/build/. It combines the server and client projects."
    }
}
task(":server:assemble").mustRunAfter(copyClientResources) (3)
1 The copyClientResources task will copy the static files generated by yarn build into the resources directory of the server project.
2 The assembleServerAndClient task ties the copy step to the existing Gradle assemble task for the server project. This means that we will get our unified archive only when running this top-level task (running server:assemble) alone will generate a "plain" WAR/JAR file without the client resources.
3 If copyClientResources is to be executed, it must be before :server:assemble. This ensures client static files will be available when the Grails app is assembled.
4 After the combined JAR file has been created, we copy the files to the top-level build directory.

In this guide, we will be using an executable JAR file as our deployment target. To enable this, edit the server project’s build.gradle file, and delete the following line:

server/build.gradle
apply plugin:"war"

3.2 Updating our config

Now that we have a unified Gradle build target, we have a couple more configuration changes to make. All of these changes have to do with the URL differences between the separate Grails & React apps and our new combined deployment.

By default, static resources in a deployed Grails app are served from the /static base URL. That will conflict with what our React app expects, but fortunately, this is a very simple change.

Edit server/grails-app/conf/application.yml:

server/grails-app/conf/application.yml
grails:
    resources:
        pattern: /**

This simply sets the static resources URL to be set to the root context.

Along similar lines, we need to edit the URL that the React app uses to make REST calls against the Grails app. This can be done within src/config.js in the client project.

Edit client/src/config.js:

client/src/config.js
import pjson from './../package.json';

const prod = process.env.NODE_ENV === 'production';  (1)

console.log(`Loading ${process.env.NODE_ENV} config...`);

export const SERVER_URL = prod ? '' : 'http://localhost:8080';

export const CLIENT_VERSION = pjson.version;
export const REACT_VERSION = pjson.dependencies.react;
1 The process.env.NODE_ENV is a NodeJS environment variable that will tell us if we are running in production or development mode. We’re using a simple ternary operator to switch the SERVER_URL config property to point to our standalone Grails app during development, and to use the root context in production.

3.3 URL Mappings

Our final change needs to happen in UrlMappings.groovy. If you recall, by default the server Grails app renders some application metadata at the root context. However, in order for our combined build to work, we need to load our React app at the root context. Fortunately, this is a very simple change as well.

Edit server/grails-app/controllers/demo/UrlMappings.groovy:

server/grails-app/controllers/demo/UrlMappings.groovy
if ( Environment.current == Environment.PRODUCTION ) { (1)
    '/'(uri: '/index.html')
} else {
    '/'(controller: 'application', action:'index')
}
1 We are using the grails.util.Environment class to determine whether we are running in development mode or in a deployed artefact, and setting the URL mappings appropriately.

With this new mapping config, when we build our executable JAR file, the / URL will be mapped to /index.html, which happens to also be the landing page for our React application.

4 Running the Application

To generate the unified executable JAR, file, simply run the following command:

~ ./gradlew assembleServerAndClient

...
JAR generated at build. It combines the server and client projects.

To run the application, use the java -jar command:

~ java -jar build/server-0.1.jar

...
Grails application running at http://localhost:8080 in environment: production

Browse to http://localhost:8080, and you should see the React application.

Make a GET request to http://localhost:8080/application/, and you should see the application metadata from the Grails application.

Congratulations, you have a unified Grails/React production build!