Skip to content

Integrate Start UI inside JHipster

Rileran edited this page Aug 30, 2022 · 1 revision

Guide to start a JHipster project with Start UI in the same repository without having to serve 2 applications in production. This will show you how to setup your repository to make JHipster Java server serve the frontend as static files in production.

This is still a work in progress, the content of this page be incorrect or outdated. If you catch a mistake in this document, you may open an issue in this repository.

The result of this tutorial can be seen in this repository. It is a result of this guide followed on the 10 of August 2022 (with JHipster 7.9.2 and Start UI from commit 5a28e62).

Generate JHipster project

mkdir my-project
cd my-project
npx generator-jhipster --skip-client

Choose the following when needed

  • Monolithic application
  • JWT

Change some Java files

In order to serve the webapp in production you will need to update and add the following files in java folder (src/main/java/com/mycompany/myapp)

Update file config/SecurityConfiguration.java

In the filterChain function, add the following lines below the .antMatchers(HttpMethod.OPTIONS, "/**")

...
.antMatchers("/app/**/*.{js,html}")
.antMatchers("/i18n/**")
.antMatchers("/content/**")
...

Update file config/WebConfigurer.java

Add imports

import static java.net.URLDecoder.decode;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;

Add the WebServerFactoryCustomizer interface to the WebConfigurer as below

public class WebConfigurer implements ServletContextInitializer, **WebServerFactoryCustomizer<WebServerFactory>** {

Add the customize , setLocationForStaticAssets and resolvePathPrefix methods

/**
 * Customize the Servlet engine: Mime types, the document root, the cache.
 */
@Override
public void customize(WebServerFactory server) {
    // When running in an IDE or with ./mvnw spring-boot:run, set location of the static web assets.
    setLocationForStaticAssets(server);
}

private void setLocationForStaticAssets(WebServerFactory server) {
    if (server instanceof ConfigurableServletWebServerFactory) {
        ConfigurableServletWebServerFactory servletWebServer = (ConfigurableServletWebServerFactory) server;
        File root;
        String prefixPath = resolvePathPrefix();
        root = new File(prefixPath + "target/classes/static/");
        if (root.exists() && root.isDirectory()) {
            servletWebServer.setDocumentRoot(root);
        }
    }
}

/**
 * Resolve path prefix to static resources.
 */
private String resolvePathPrefix() {
    String fullExecutablePath = decode(this.getClass().getResource("").getPath(), StandardCharsets.UTF_8);
    String rootPath = Paths.get(".").toUri().normalize().getPath();
    String extractedPath = fullExecutablePath.replace(rootPath, "");
    int extractionEndIndex = extractedPath.indexOf("target/");
    if (extractionEndIndex <= 0) {
        return "";
    }
    return extractedPath.substring(0, extractionEndIndex);
}

Create file config/StaticResourcesWebConfiguration.java

With the following content

package com.mycompany.myapp.config;

import java.util.concurrent.TimeUnit;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.http.CacheControl;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import tech.jhipster.config.JHipsterConstants;
import tech.jhipster.config.JHipsterProperties;

@Configuration
@Profile({ JHipsterConstants.SPRING_PROFILE_PRODUCTION })
public class StaticResourcesWebConfiguration implements WebMvcConfigurer {

    protected static final String[] RESOURCE_LOCATIONS = new String[] {
        "classpath:/static/",
        "classpath:/static/content/",
        "classpath:/static/i18n/",
    };
    protected static final String[] RESOURCE_PATHS = new String[] {
        "/*.js",
        "/*.css",
        "/*.svg",
        "/*.png",
        "*.ico",
        "/content/**",
        "/i18n/*",
    };

    private final JHipsterProperties jhipsterProperties;

    public StaticResourcesWebConfiguration(JHipsterProperties jHipsterProperties) {
        this.jhipsterProperties = jHipsterProperties;
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        ResourceHandlerRegistration resourceHandlerRegistration = appendResourceHandler(registry);
        initializeResourceHandler(resourceHandlerRegistration);
    }

    protected ResourceHandlerRegistration appendResourceHandler(ResourceHandlerRegistry registry) {
        return registry.addResourceHandler(RESOURCE_PATHS);
    }

    protected void initializeResourceHandler(ResourceHandlerRegistration resourceHandlerRegistration) {
        resourceHandlerRegistration.addResourceLocations(RESOURCE_LOCATIONS).setCacheControl(getCacheControl());
    }

    protected CacheControl getCacheControl() {
        return CacheControl.maxAge(getJHipsterHttpCacheProperty(), TimeUnit.DAYS).cachePublic();
    }

    private int getJHipsterHttpCacheProperty() {
        return jhipsterProperties.getHttp().getCache().getTimeToLiveInDays();
    }
}

Create the file web/rest/ClientForwardController.java

With the following code

package com.mycompany.myapp.web.rest;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class ClientForwardController {

    /**
     * Forwards any unmapped paths (except those containing a period) to the client {@code index.html}.
     * @return forward to client {@code index.html}.
     */
    @GetMapping(value = "/**/{path:[^\\.]*}")
    public String forward() {
        return "forward:/";
    }
}

Update the pom.xml

In the pom.xml file, below the lines

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <executions>
        <execution>
            <goals>
                <goal>build-info</goal>
            </goals>
        </execution>
    </executions>
</plugin>

add the following lines :

<plugin>
    <groupId>com.github.eirslett</groupId>
    <artifactId>frontend-maven-plugin</artifactId>
    <configuration>
        <workingDirectory>webapp</workingDirectory>
    </configuration>
    <executions>
        <execution>
            <id>install node and yarn</id>
            <goals>
                <goal>install-node-and-yarn</goal>
            </goals>
            <configuration>
                <nodeVersion>v16.14.0</nodeVersion>
                <yarnVersion>v1.22.15</yarnVersion>
            </configuration>
        </execution>
        <execution>
            <id>yarn install</id>
            <goals>
                <goal>yarn</goal>
            </goals>
            <configuration>
                <arguments>install</arguments>
            </configuration>
        </execution>
        <execution>
            <id>yarn build</id>
            <goals>
                <goal>yarn</goal>
            </goals>
            <configuration>
                <arguments>build</arguments>
            </configuration>
        </execution>
    </executions>
</plugin>

Update the .gitignore

Add the following lines to the root .gitignore

#######################
# Maven frontend plugin
#######################
/webapp/node

Update the application-dev.yml

Add http://localhost:3000 and https://localhost:3000 tojhipster.cors.allowed-origins

allowed-origins: 'http://localhost:8100,https://localhost:8100,http://localhost:9000,https://localhost:9000,http://localhost:3000,https://localhost:3000'

Update the jhipster.mail.base-url with http://localhost:3000/app

  mail: # specific JHipster mail property, for standard properties see MailProperties
    base-url: http://localhost:3000/app

(Required if lang!=’en’) Update EmailServiceIT.java

As of JHipster 7.7.0, if your project uses localization features, but does not provide an English translation, some tests in [EmailServiceIT.java](http://EmailServiceIT.java) might fail. To correct them, update line in testSendEmailFromTemplate

 user.setLangKey("en");

with

user.setLangKey(Constants.DEFAULT_LANGUAGE);

(Optional) Update package.json for convenience

Add the following lines at the end of the scripts property inside package.json .

{
  ...
  "scripts": {
  ...
  "prettier:format": "prettier --write \"{,src/**/}*.{md,json,yml,html,java}\"",
  "webapp:build": "cd webapp && yarn build",
  "webapp:dev": "cd webapp && yarn dev",
  "webapp:install": "cd webapp && yarn install",
  "webapp:storybook": "cd webapp && yarn storybook",
  "webapp:test": "cd webapp && yarn test"
  },
  ...
}

Generate Start UI

At the root of the project run the following command (you need node 14+)

npx create-start-ui --web --no-git-init webapp

Update the webapp/package.json

Update the scripts and the lint-staged sections with the following code

"scripts": {
    "postinstall": "yarn build:info && yarn theme:generate-typing",
    "test": "cypress open",
    "test:ci": "cypress run --component",
    "dev": "yarn docs:build && next dev",
    "build": "yarn build:info && yarn docs:build && NEXT_PUBLIC_API_BASE_URL=/api next build && next export && mkdir -p ../target/classes/static && mv out/* ../target/classes/static",
    "build:info": "node .build-info.generate.js",
    "pretty": "prettier -w .",
    "lint": "eslint ./src --fix && tsc --noEmit",
    "lint:staged": "tsc --noEmit && eslint --cache --fix",
    "storybook": "start-storybook -p 6006",
    "storybook:build": "build-storybook && mv ./storybook-static ./public/storybook",
    "theme:generate-typing": "chakra-cli tokens ./src/theme/theme.ts",
    "theme:generate-icons": "svgr --config-file src/components/Icons/svgr.config.js src/components/Icons/svg-sources",
    "docs:build": "swagger-cli bundle src/mocks/openapi/openapi.yaml -t json --outfile public/open-api.json"
  },
  "lint-staged": {
    "*.{ts,tsx,js,jsx,json}": "prettier --write",
    "*.{ts,tsx,js,jsx}": "yarn lint:staged",
    "src/mocks/**/*.{yaml,yml}": "yarn docs:build"
  },

Create the webapp/.env file for development

NEXT_PUBLIC_API_BASE_URL=http://localhost:8080/api

Update .env.validator.js file

Change line

NEXT_PUBLIC_API_BASE_URL: z.string().url().optional(),

with

NEXT_PUBLIC_API_BASE_URL: z.string().optional(),

Run in dev

In two terminals run the following commands

./mvnw
cd webapp && yarn dev

The web app will be available on http://localhost:3000

Build for production

Build the java app

./mvnw -Pprod clean verify