Full Stack Development – Quarkus and Angular with Quinoa

Prerequisites

  • Tools: IDE (IntelliJ IDEA), git, Quarkus cli, Angular cli, node, podman
  • Understanding: Quarkus basics, REST API, Angular basics

One Big App

Sometimes, simpler is better. Not everything has to be overly optimized for the best performance possible. As for most lower traffic applications, building a complex multi-application microservice architecture to break out the frontend development with the backend development is overkill. Wouldn’t it be nice to create your REST API and package your browser-based single-page application (SPA) all in one container?

That’s where Quinoa comes in handy. Quinoa is a Quarkus extension, available as part of the Quarkiverse, that allows you to quickly and easily engage in full stack development without the hassles of managing multiple project lifecycles. Quinoa aims to enhance the developer experience by simplifying the integration of Quarkus with modern web frameworks and providing efficient tools for building and developing user interfaces.

In this blog post, we will create a new Quarkus application, add the Quinoa extension, and then setup an Angular app which will all run seamlessly in a single process.

Create the Quarkus Application

To begin the process, we need to create a new Quarkus application. We could use our favorite IDE’s built-in tooling to help, but instead we are going to use the Quarkus cli to not only help with project initialization, but throughout the development process. To get the cli installed, follow the instructions at https://quarkus.io/guides/cli-tooling. Once the cli is installed, we can create our new Quarkus application.

quarkus create app com.snimmo:quarkus-angular-quinoa-example
cd quarkus-angular-quinoa-example
quarkus ext add config-yaml resteasy-reactive-jackson
mv src/main/resources/application.properties src/main/resources/application.yml

In this block, we accomplished several goals.

  1. Created a new Quarkus application which ends up in a folder named quarkus-angular-quinoa-example
  2. Added some of the basic extensions we are definitely going to use in the project.
  3. One of the extensions, quarkus-config-yaml, allows use to use yaml configs rather than properties files. I find this helpful because I am knee-deep in yaml all day anyway (thanks, k8s) and the structures are less verbose. We are simply renaming the generated file.

First Quinoa, then Angular

The point of Quinoa is to not be required to create a separate Angular project. We are going to create, develop and serve up our Angular app in the same repository. To get started, let’s add the Quinoa extension and get the basic project structure created for our Angular application.

export NG_PROJECT_NAME=example-ng
quarkus ext add quinoa
yq -i '.quarkus.quinoa.enable-spa-routing = true' src/main/resources/application.yml
yq -i '.quarkus.quinoa.build-dir = "dist/'$NG_PROJECT_NAME'/browser"' src/main/resources/application.yml
mkdir src/main/webui
ng new --routing --style scss --directory src/main/webui -g --ssr false --standalone false $NG_PROJECT_NAME

In this block, we got a couple things done here.

  1. We added the Quinoa extension to the project.
  2. We are going to be building an Angular SPA. To enable Quinoa to properly serve a SPA, we need to add the enable-spa-routing flag set to true.
  3. With Angular 17, the builder will output publicly accessible files in the browser directory, ssr files in the server directory. Because of this, we need to add more specificity to the build process to tell it where the artifacts are located. So we add one more item to the Quinoa configuration to specify the build directory. More info at: https://github.com/angular/angular-cli/issues/26304#issuecomment-1804389033
  4. By default, Quinoa is going to serve the Angular application from the src/main/webui folder so instead of trying to be fancy, let’s just use the default and create the folder.
  5. The last command creates our new Angular application. The command adds routing, sets the style to scss and sets the directory of where to put the starter application.
    • With Angular 17 release, the Angular cli gives you an option to create an application with Server-Side Rendering (SSR). Quinoa doesn’t play well with this in terms of routing, so we turn this off for our use here. If the performance of your Angular app in terms of scale requires this to be enabled, that’s a good sign the Angular app should be a standalone application and probably deployed into a CDN.
    • The default app now with 17 is also built as a standalone. I’m a heavy user of NgModules to provide support in common aspects of the application, routing and making imports/exports easier. Because of this, I set standalone to false to add NgModules into the starting application.

At this point, you should be able to run the quarkus application (use the cli, quarkus dev) and see the starter application at http://localhost:8080. When you created the application, there is a ‘hello world’ endpoint also created representing a starting point for your API. That should work as well at http://localhost:8080/hello.

Conclusion

This setup provides a really powerful local development experience. Any time you make changes to the Angular app, then the SPA rebuilds and provides an immediate update to the UI. If you make changes to the Quarkus side of the app, then the Quarkus dev lifecycle takes care of things and does a hot-reload of the backend code as well. Pretty much all your changes, with a few exceptions, don’t require any restarting or refreshing. You can even run the continuous testing feature in the background as you develop.

Additionally, the entire package can easily be packaged together and served in Quarkus. Run quarkus build and the correct jars will be generated. After that, you can build and run the entire application in a container using the Dockerfiles that come generated in the project.

podman build -f src/main/docker/Dockerfile.jvm -t quarkus/quarkus-angular-quinoa-example-jvm .
podman run -i --rm -p 8080:8080 quarkus/quarkus-angular-quinoa-example-jvm