JAX-RS Crud Application using Quarkus and Vue.js

In the first tutorial of this series (JAX-RS CRUD Application using Vue.js and RESTEasy), we have learned how to create a JAX-RS Application using Vue.js as Front end and WildFly application server on the Back end side. We will now show how to migrate our Java Enterprise application to a Quarkus application without changing our Web layer.

Bootstrap the Quarkus application

We have a variety of options to create a basic template for our project. We will be using the online Web application which is available at: https://code.quarkus.io/

As you can see, we have checked several extensions: RESTEasy will be used as JAX-RS implementation, Hibernate and Agroal for the persistence and connection pooling, JSONB to produce content in JSON format and the H2 Database for storing our data.

Save your project and import it in your IDE. If you check the pom.xml file, you will notice that the following set of dependencies have been included:

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>${quarkus.platform.group-id}</groupId>
        <artifactId>${quarkus.platform.artifact-id}</artifactId>
        <version>${quarkus.platform.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-hibernate-orm</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-agroal</artifactId>
    </dependency>

    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-jdbc-h2</artifactId>
    </dependency>

    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy-jsonb</artifactId>
    </dependency>
 
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-junit5</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>io.rest-assured</groupId>
      <artifactId>rest-assured</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

Now let’s see what need to be changed from our former WildFly project. The Entity class, uses the same javax.persistence.* set of libraries so it does not need to be changed at all:

@Entity
@NamedQuery(name = "Customers.findAll",
        query = "SELECT c FROM Customer c ORDER BY c.id")
public class Customer {
    @Id
    @SequenceGenerator(
            name = "customerSequence",
            sequenceName = "customerId_seq",
            allocationSize = 1,
            initialValue = 1)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "customerSequence")
    private Long id;
    @Column(length = 40)
    private String name;
    @Column(length = 40)
    private String surname;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSurname() {
        return surname;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }

}

Then we go to the CustomerRepository class:

import javax.enterprise.context.ApplicationScoped;

import java.util.List;

import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.transaction.Transactional;
import javax.ws.rs.WebApplicationException;


@ApplicationScoped
public class CustomerRepository {

    @Inject
    EntityManager entityManager;

    public List<Customer> findAll() {
        return entityManager.createNamedQuery("Customers.findAll", Customer.class)
                .getResultList();
    }

    public Customer findCustomerById(Long id) {

        Customer customer = entityManager.find(Customer.class, id);

        if (customer == null) {
            throw new WebApplicationException("Customer with id of " + id + " does not exist.", 404);
        }
        return customer;
    }
    @Transactional
    public void updateCustomer(Long id, String name, String surname) {

        Customer customerToUpdate = findCustomerById(id);
        customerToUpdate.setName(name);
        customerToUpdate.setSurname(surname);
    }
    @Transactional
    public void createCustomer(Customer customer) {

        entityManager.persist(customer);

    }
    @Transactional
    public void deleteCustomer(Long customerId) {

        Customer c = findCustomerById(customerId);
        entityManager.remove(c);
    }


}

Here we can see a first subtle difference with Java EE. In Quarkus we can @Inject direclty the EntityManager object which, as the class says, managed directly Entities. In Java EE we need the help of the PersistenceContext to make our Entity available:

@PersistenceContext
private EntityManager entityManager;

In a nutshell the PersistenceContext defines a scope under which particular entity instances are created, persisted, and removed and each EntityManager instance is associated with a PersistenceContext. That being said, in the Java EE world you can still @Inject the EntityManager in your Beans, but you have to create a Producer for it.

Let’s move to the Endpoint class, which does not differ from the one used in WildFly:

@Path("customers")
@ApplicationScoped
@Produces("application/json")
@Consumes("application/json")
public class CustomerEndpoint {

    @Inject CustomerRepository customerRepository;

    @GET
    public List<Customer> getAll() {
        return customerRepository.findAll();
    }

    @POST
    public Response create(Customer customer) {
        customerRepository.createCustomer(customer);
        return Response.status(201).build();
    }

    @PUT
    public Response update(@QueryParam("id") Long id, @QueryParam("name") String name, @QueryParam("surname") String surname) {
        customerRepository.updateCustomer(id,name,surname);
        return Response.status(204).build();
    }
    @DELETE
    public Response delete(@QueryParam("id") Long customerId) {
        customerRepository.deleteCustomer(customerId);
        return Response.status(204).build();
    }

}

One more different with the Java EE application is that we don’t need a JAX-RS Activator class in Quarkus. We already get by default our REST Services exposed under the Root Web Context (“/”).

Adding the Database

WildFly application server ships out of the box with an H2 Database at start up. In order to boostrap the H2 Database when running Quarkus in development mode, you can use an Application Scoped CDI Bean which overrides the observeContextInit, onStart and onStop method to manage the lifecycle of H2 Database as part of your application:

@ApplicationScoped
public class H2DatabaseStarter {

    protected final Logger log = LoggerFactory.getLogger(this.getClass());

    // H2 Database
    private Server tcpServer;


    public void observeContextInit(@Observes @Initialized(ApplicationScoped.class) Object event) {
        try {

            tcpServer =  Server.createTcpServer("-tcpPort", "19092", "-tcpAllowOthers").start();
            log.info("H2 database started in TCP server mode on Port 19092");

        } catch (SQLException e) {

            throw new RuntimeException(e);

        }
    }

    void onStart(@Observes StartupEvent ev) {
        log.info("Application is starting");

    }


    void onStop(@Observes ShutdownEvent ev) {

        if (tcpServer != null) {
            tcpServer.stop();
            log.info("H2 database was shut down");
            tcpServer = null;
        }
    }
}

Please notice, if you want to manage your H2 database externally, you can simply start the H2 Server in tcp mode. Check this tutorial for more information: H2 database tutorial

From Enterprise configuration to Quarkus configuration

The last bit we need is the application configuration which mainly covers the Database settings. In Quarkus the persistence.xml file is not used to define the Persistence settings but the configuration is maintained in the default application.properties file:

quarkus.datasource.url=jdbc:h2:tcp://localhost:19092/mem:test
quarkus.datasource.driver=org.h2.Driver
quarkus.datasource.username=username-default

quarkus.datasource.initial-size=1
quarkus.datasource.min-size=2
quarkus.datasource.max-size=8
quarkus.hibernate-orm.sql-load-script=import.sql
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.log.sql=true

Please note that we have configured to load the import.sql script, which as for the Enterprise counterpart, insert some Entities at start up:

INSERT INTO customer (id, name, surname) VALUES ( nextval('customerId_seq'), 'Homer','Simpson');
INSERT INTO customer (id, name, surname) VALUES ( nextval('customerId_seq'), 'Bart','Simpson');

Adding the Web layer

The Web layer of our application relies on Vue.js to create a REST CRUD set of methods:

<div id="app">

    <table class="blueTable">
        <thead >
        <tr>
            <th>#</th>
            <th>Name</th>
            <th>Surname</th>
            <th>Delete</th>
            <th>Update</th>
        </tr>
        </thead>
        <tbody>
        <tr v-for="user in users" :key="user.id">
            <td>{{user.id}}</td>
            <td>{{user.name}}</td>
            <td>{{user.surname}}</td>
            <td>  <button class="button"  @click="deleteUser(user.id)">Delete</button></td>
            <td>  <button class="button"  @click="updateUser(user.id)">Update</button></td>
        </tr>
        </tbody>
    </table>
    <br/>
    <div>
        <form name="form" >
            <input type="text" placeholder="Name" v-model="name" size="60"/><br/>
            <input type="text" placeholder="Surname" v-model="surname" size="60"/><br/>
            <input type="submit" value="Save" @click="createUser"/><br/>
        </form>
    </div>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.min.js"></script>
<script>


new Vue({
    el: "#app",
    data() {
        return {
            users: []
        }
    },
    methods: {


        updateUser: function(id) {

                axios.put("/customers?id="+id+"&name="+this.name+"&surname="+this.surname)
                .then(response => {
                    this.listUsers()
                })
                this.name='';
                this.surname='';
        },

        deleteUser: function(id) {

                axios.delete("/customers?id="+id)
                .then(response => {
                    this.listUsers()
                })
        },

        createUser: function() {

          var user = {
                // name and surname should exist in [data] or [computed] section
                name: this.name,
                surname: this.surname
              };


            axios.post("/customers",  user)
                .then(response => {
                    this.listUsers()
                })


        },
        listUsers: function() {
            axios.get("/customers")
                .then(response => {
                    this.users = response.data
                })
        }
    },
    mounted: function() {

       this.listUsers()
    }


})
</script>

In the first tutorial, we have covered some details about Vue.js functions. What we have to consider here is that Quarkus runs, by default, on the Root Web Context (“/”) and does not require a REST Web context (e.g. “/rest”). Therefore we can access the “/customers” without any coupling with the application name or rest context.

Here is your complete Quarkus application:

src
├── main
│   ├── docker
│   │   ├── Dockerfile.jvm
│   │   └── Dockerfile.native
│   ├── java
│   │   └── com
│   │       └── mastertheboss
│   │           └── jaxrs
│   │               ├── CustomerEndpoint.java
│   │               ├── CustomerException.java
│   │               ├── Customer.java
│   │               ├── CustomerRepository.java
│   │               └── H2DatabaseStarter.java
│   └── resources
│       ├── application.properties
│       ├── import.sql
│       └── META-INF
│           └── resources
│               └── index.html

We can run it in Java development mode as follows:

mvn install quarkus:dev

And here is the application in action:

Summing up

So to recap, we have performed the following actions to transform our original Java EE application in a Quarkus application:

  • We have updated the project dependencies to use the “io.quarkus” Group.
  • We applied some minimal updates to our CDI beans, which are able to @Inject the EntityManager produced by Quarkus ORM extensions
  • We have departed from the persistence.xml/web.xml configuration to the application.properties file
  • We have created a class to bootstrap the H2 Database
  • Finally, we got rid of the JaxRS activator class, which is not needed anymore