Integración de React + Quarkus usando Maven
Introducción
En este post te mostraré cómo servir una aplicación front-end ReactJS (o cualquier otra SPA de JavaScript) usando una aplicación Java Quarkus como back-end y servidor de páginas estáticas. El post también incluye instrucciones sobre cómo configurar Maven para realizar las tareas de construcción del front-end junto con el empaquetado de tu aplicación.
Este artículo es muy similar a mi post de blog de 2017 Integración de Angular + Spring Boot usando Gradle, con algunas características adicionales para habilitar el funcionamiento del router de React.
Requisitos
Las librerías y tecnologías necesarias para este ejemplo son:
El objetivo principal es lograr un npm install + build que se integre con el plugin de recursos de Maven y despliegue los archivos de distribución a un directorio donde Quarkus pueda servirlos. Quarkus actuará entonces como el servicio back-end así como el gateway API para servir el front-end también.
Puedes encontrar el código para estas configuraciones en mi aplicación quick start YAKC – Kubernetes Dashboard para YAKC (Yet Another Kubernetes Client).
Back-end (Quarkus)
En este nuevo mundo de Microservicios, tiene sentido que el front-end y el/los back-end(s) sean servicios diferentes que usualmente son servidos y orquestados por un gateway API. Es difícil encontrar esas antiguas aplicaciones web donde el front-end y back-end eran parte del mismo servicio.

Esta arquitectura distribuida tiene sentido para proyectos empresariales grandes, pero a veces solo queremos hacer un prototipo o construir rápidamente una aplicación web simple. Ese es el momento cuando empiezas a extrañar esos viejos monolitos y su simplicidad que te permitía crear y especialmente desplegar tu aplicación rápidamente.
El objetivo principal es lograr que Quarkus actúe como un gateway API. Así que además de proporcionar la API REST, sirve los archivos del front-end también. De esta manera conseguimos mantener ReactJS como nuestra tecnología principal de front-end, Quarkus como nuestro back-end, y evitar todas las complejidades extra necesarias para un despliegue de 2 (o más) servicios separados.
Puedes pensar que Quarkus ya proporciona una manera de servir recursos estáticos y que la solución es tan simple como almacenar los archivos construidos del front-end en este directorio antes de construir el back-end. Sin embargo, esto no permitirá que el router de React funcione y no podrías enrutar /nested/front-end/routed/paths
(Solo intenta refrescar tu navegador en uno de esos paths anidados).
Clase GatewayResource
@Path("/")
public class GatewayResource {
private static final String FALLBACK_RESOURCE = "/frontend/index.html";
private final ApiResource apiResource;
@Inject
public GatewayResource(ApiResource apiResource) {
this.apiResource = apiResource;
}
@Path("/api/v1")
public ApiResource getApiResource() {
return apiResource;
}
@GET
@Path("/")
public Response getFrontendRoot() throws IOException {
return getFrontendStaticFile("index.html");
}
@GET
@Path("/{fileName:.+}")
public Response getFrontendStaticFile(@PathParam("fileName") String fileName) throws IOException {
final InputStream requestedFileStream = GatewayResource.class.getResourceAsStream("/frontend/" + fileName);
final InputStream inputStream = requestedFileStream != null ?
requestedFileStream :
GatewayResource.class.getResourceAsStream(FALLBACK_RESOURCE);
final StreamingOutput streamingOutput = outputStream -> IOUtils.copy(inputStream, outputStream);
return Response
.ok(streamingOutput)
.cacheControl(CacheControl.valueOf("max-age=900"))
.type(URLConnection.guessContentTypeFromStream(inputStream))
.build();
}
}
Esta clase es el punto de entrada principal para cualquier solicitud HTTP que la aplicación recibe.
El primer método, getApiResource
delega cualquier solicitud a /api/v1
a la instancia subresource ApiResource
que se encargará de ella.
El getFrontendRoot
se encargará de cualquier solicitud al path raíz /
solicitando el archivo index.html
al getFrontendStaticFile
que describiré a continuación.
Finalmente, el getFrontendStaticFile
se encargará del resto de las solicitudes. El método primero trata de localizar cualquier archivo con el nombre de archivo proporcionado en el directorio de recursos /frontend
. Este directorio debería ser empaquetado durante la fase de construcción de la aplicación y contiene la aplicación ReactJS construida.
Si no se encuentra ningún archivo con este nombre, servirá el archivo index.html
. Esta es la parte que permite que el router de React funcione. Cualquier solicitud no coincidente ya sea por el subresource de API o un archivo de front-end servirá la aplicación React que luego se encargará de realizar un enrutamiento de "front-end".
Front-end (React)
Tu aplicación front-end completa puede estar ubicada en cualquier lugar dentro de tu proyecto, en mi caso la he ubicado bajo src/main/frontend
. Además de notar la ubicación del path para tu aplicación React, realmente no necesitas ninguna configuración extra para que este procedimiento funcione.
Construcción Maven
Para el paso final de integrar el front-end React con el back-end Quarkus necesitas afinar la construcción Maven.
Recursos
<!-- ... -->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
<resource>
<directory>src/main/frontend/build</directory>
<targetPath>frontend</targetPath>
</resource>
</resources>
<!-- ... -->
</build>
<!-- ... -->
En este primer extracto de código, he añadido una nueva entrada a la configuración de recursos de construcción.
Además del estándar src/main/resources
, el directorio donde la aplicación React es construida src/main/frontend/build
también será ensamblado y copiado al directorio target/classes/frontend
.
Si recuerdas, en la clase GatewayResource
, estaba cargando recursos estáticos desde este directorio.
Npm build + install
<!-- ... -->
<profiles>
<profile>
<id>build-frontend</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>${version.exec-maven-plugin}</version>
<executions>
<execution>
<id>npm-install</id>
<phase>generate-resources</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<workingDirectory>src/main/frontend</workingDirectory>
<executable>npm</executable>
<arguments>
<argument>install</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>npm-build</id>
<phase>generate-resources</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<workingDirectory>src/main/frontend</workingDirectory>
<executable>npm</executable>
<arguments>
<argument>run</argument>
<argument>build</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<!-- ... -->
</profiles>
El segundo extracto de código se usa para ejecutar npm install
y npm run build
desde Maven. Añadí esto como un perfil separado porque usualmente realizo estos pasos manualmente mientras estoy en mi máquina local. Solo los invoco en el CI, así que mi paso de construcción de CI se ve como mvn -Pbuild-frontend clean package
. Sin embargo, no hay problema en añadir estas configuraciones directamente a la sección de construcción de tu pom.xml
.
Como puedes ver configuré 2 ejecuciones para el Exec Maven Plugin. La primera invocará npm install
usando el directorio home del front-end (src/main/frontend
) como su directorio de trabajo. La segunda ejecución tiene una configuración similar pero ejecuta npm run build
en su lugar.
Ejecución
Una vez que tu aplicación está empaquetada, puedes ejecutarla ya sea ejecutando el archivo jar generado (java -jar ./target...
) o ejecutando mvn quarkus:dev
.

Modo de desarrollo
Si quieres disfrutar y beneficiarte de la recarga en caliente/vivo tanto de Quarkus como de React necesitarás alguna configuración extra.
En este caso, ejecutaremos el front-end usando el estándar npm start
y apuntando nuestro navegador a http://localhost:3000
. Y ejecutaremos nuestro back-end usando mvn quarkus:dev
en http://localhost:8080
.
Back-end
Dado que el front-end es servido desde una URL diferente, necesitamos habilitar CORS en Quarkus.
Podemos hacer esto modificando nuestro application.properties
para incluir:
quarkus.http.cors=true
quarkus.http.cors.origins=http://localhost:3000
quarkus.http.cors.headers=accept, origin, authorization, content-type, x-requested-with
quarkus.http.cors.methods=GET,POST,DELETE,OPTIONS
Estas propiedades habilitan CORS para permitir solicitudes desde el origen de desarrollo del front-end. Puedes modificar las entradas para hacerlo más o menos restrictivo dependiendo de tus necesidades.
Front-end
Para la parte del front-end, necesitamos afinar nuestra aplicación React para acceder a diferentes endpoints del back-end dependiendo del entorno en el que la estemos ejecutando.
En mi caso, dado que estoy usando create-react-app, añadí dos archivos de propiedades de entorno:
.env
para producción:
REACT_APP_API_URL=/api/v1
.env.development
para desarrollo:
REACT_APP_API_URL=http://localhost:8080/api/v1
Una vez que estos dos archivos están listos, create-react-app se encarga de los reemplazos apropiados en tu aplicación dependiendo de tu entorno objetivo. Para usar esos valores, necesitarás referenciar la URL usando la variable process.env.REACT_APP_API_URL
:
const requestDelete = async pod => {
await fetch(
`${process.env.REACT_APP_API_URL}/pods/${metadata.selectors.namespace(pod)}/${metadata.selectors.name(pod)}`,
{method: 'DELETE'}
);
}
Conclusión
En este post, te he mostrado cómo servir tanto una aplicación front-end JavaScript como una aplicación back-end Java usando Quarkus. Quarkus actúa como un gateway API y ya sea sirve los archivos estáticos del front-end o procesa la solicitud del back-end usando recursos y servicios. También te he enseñado cómo combinar y usar la recarga en caliente en React y Quarkus afinando la configuración para cada aplicación.
He extraído el código para este post de mi YAKC – Kubernetes Dashboard quickstart. Puedes aprender más visitando el sitio del proyecto GitHub de YAKC. También puedes ver el código relacionado aquí.