Creating an Ajax front-end to a Quarkus REST application

Learn how to query a Quarkus 3.x REST application using an Ajax/jQuery front-end. Updated for Quarkus 3.x, featuring RESTEasy Reactive, Mutiny reactive types (Uni), and the Jakarta EE 10 namespace (jakarta.*).

In this article we will check out how to query a REST Service running with Quarkus 3.x using a minimal Ajax and jQuery client.

Quarkus applications can be quickly bootstrapped using the Maven or Gradle plugin. With Quarkus 3.x, RESTEasy Reactive is the recommended high-performance REST stack. The CLI or Maven plugin generates a minimal project structure with standard REST dependencies. Let’s create a project using Maven:

mvn io.quarkus.platform:quarkus-maven-plugin:3.8.1:create \
    -DprojectGroupId=com.sample \
    -DprojectArtifactId=hello-quarkus \
    -DclassName="com.sample.DemoEndpoint" \
    -Dpath="/persons" \
    -Dextensions="resteasy-reactive-jackson"

Notice that in Quarkus 3.x, all Jakarta EE components have migrated from the legacy javax.* packages to jakarta.*. Additionally, RESTEasy Reactive allows us to return reactive Mutiny types like Uni or standard blocking payloads effortlessly.

Within our DemoEndpoint class, let’s add methods to perform CRUD operations using Jakarta REST and SmallRye Mutiny:

package com.sample;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import io.smallrye.mutiny.Uni;
import java.util.List;

@Path("persons")
@ApplicationScoped
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class DemoEndpoint {

    @Inject 
    DemoRepository demoRepository;

    @GET
    public Uni<List<Person>> getAll() {
        return Uni.createFrom().item(() -> demoRepository.findAll());
    }

    @POST
    public Uni<Response> create(Person p) {
        demoRepository.createPerson(p);
        return Uni.createFrom().item(() -> Response.status(Response.Status.CREATED).build());
    }

    @PUT
    public Uni<Response> update(Person p) {
        demoRepository.updatePerson(p);
        return Uni.createFrom().item(() -> Response.status(Response.Status.NO_CONTENT).build());
    }

    @DELETE
    public Uni<Response> delete(@QueryParam("id") Integer id) {
        demoRepository.deletePerson(id);
        return Uni.createFrom().item(() -> Response.status(Response.Status.NO_CONTENT).build());
    }
}

The Repository class uses an in-memory data structure to store person objects:

package com.sample;

import jakarta.enterprise.context.ApplicationScoped;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;

@ApplicationScoped
public class DemoRepository {

    private final List<Person> list = new CopyOnWriteArrayList<>();
    private final AtomicInteger counter = new AtomicInteger(1);

    public int getNextCustomerId() {
        return counter.getAndIncrement();
    }

    public List<Person> findAll() {
        return list;
    }

    public Person findPersonById(Integer id) {
        return list.stream()
                .filter(c -> c.getId().equals(id))
                .findFirst()
                .orElseThrow(() -> new RuntimeException("Person not found!"));
    }

    public void updatePerson(Person person) {
        Person personToUpdate = findPersonById(person.getId());
        personToUpdate.setName(person.getName());
        personToUpdate.setSurname(person.getSurname());
    }

    public void createPerson(Person person) {
        person.setId(getNextCustomerId());
        list.add(person);
    }

    public void deletePerson(Integer id) {
        Person c = findPersonById(id);
        list.remove(c);
    }
}

Then, here is the POJO class named Person:

package com.sample;

public class Person {
    private Integer id;
    private String name;
    private String surname;

    public Person() {
    }

    public Person(Integer id, String name, String surname) {
        this.id = id;
        this.name = name;
        this.surname = surname;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", surname='" + surname + '\'' +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer 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;
    }
}

Done with the server side. Now let’s place an index.html page in the src/main/resources/META-INF/resources/ folder to display the List of Person objects in an HTML table using Ajax:

<html>
<head>
    <!-- little bit of css to beautify the table -->
    <link rel="stylesheet" type="text/css" href="stylesheet.css" media="screen" />
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>

    <script type="text/javascript">
$(document).ready(function () {
    $.getJSON("/persons",
    function (json) {
        var tr;
        for (var i = 0; i < json.length; i++) {
            tr = $('<tr/>');
            tr.append("<td>" + json[i].id + "</td>");
            tr.append("<td>" + json[i].name + "</td>");
            tr.append("<td>" + json[i].surname + "</td>");
            $('table tbody').append(tr);
        }
    });
});
</script>
</head>

<body>
<div align="left" style="margin-top: 5%;">
    <fieldset style="border: none;">
        <legend><strong>Users List</strong></legend>

        <!-- table to show data -->
        <table class="greyGridTable">
            <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Surname</th>
            </tr>
            </thead>
            <tbody>

            </tbody>
        </table>
    </fieldset>
</div>

</body>
</html>

Some CSS style has been included in the file stylesheet.css (place it under src/main/resources/META-INF/resources/):

table.greyGridTable {
  border: 2px solid #FFFFFF;
  width: 100%;
  text-align: center;
  border-collapse: collapse;
}
table.greyGridTable td, table.greyGridTable th {
  border: 1px solid #FFFFFF;
  padding: 3px 4px;
}
table.greyGridTable tbody td {
  font-size: 13px;
}
table.greyGridTable td:nth-child(even) {
  background: #EBEBEB;
}
table.greyGridTable thead {
  background: #FFFFFF;
  border-bottom: 4px solid #333333;
}
table.greyGridTable thead th {
  font-size: 15px;
  font-weight: bold;
  color: #333333;
  text-align: center;
  border-left: 2px solid #333333;
}
table.greyGridTable thead th:first-child {
  border-left: none;
}

table.greyGridTable tfoot td {
  font-size: 14px;
}

That’s all. Make sure the RESTEasy Reactive Jackson dependencies are included in your pom.xml file:

<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-resteasy-reactive-jackson</artifactId>
</dependency>
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-resteasy-reactive</artifactId>
</dependency>

Run the application in developer mode:

$ mvn quarkus:dev

Now we can try adding some data using cURL:

$ curl -d '{"name":"john", "surname":"black"}' -H "Content-Type: application/json" -X POST http://127.0.0.1:8080/persons
$ curl -d '{"name":"victor", "surname":"frankenstein"}' -H "Content-Type: application/json" -X POST http://127.0.0.1:8080/persons

Let’s navigate to http://localhost:8080 in your browser to verify that the data is listed in our HTML table:

quarkus tutorial json ajax

Great! We have seen how easily JSON data can be processed reactively on Quarkus 3.x and dynamically rendered on the front end using jQuery and Ajax.

Frequently Asked Questions (FAQs)

1. Why migrate from RESTEasy Classic to RESTEasy Reactive in Quarkus 3.x?

RESTEasy Reactive is engineered specifically for Quarkus's non-blocking architecture. It improves throughput, decreases memory usage, and seamlessly supports Mutiny reactive types (e.g., Uni and Multi) alongside traditional blocking endpoints.

2. What changed between Java EE and Jakarta EE in Quarkus 3?

Quarkus 3 updated to Jakarta EE 10, replacing all legacy javax.* annotations (such as javax.ws.rs.* and javax.enterprise.context.*) with their standard jakarta.* equivalents.

3. How do I enable CORS if my Ajax client runs on a different port or host?

You can enable CORS in Quarkus by adding quarkus.http.cors=true to your src/main/resources/application.properties file, along with optional origin configurations like quarkus.http.cors.origins=http://localhost:3000.


Recommended Articles

Run Quarkus 3 Application with Jakarta REST Service and Vue.js Front-End

Learn how to run a Quarkus 3 application using Jakarta REST Service and Vue.js. Includes CRUD methods wrapped in Vue.js functions.

Quarkus REST Client API Tutorial: MicroProfile REST Client Implementation

Learn how to develop Quarkus REST Client using MicroProfile REST Client with a basic example and configuration.

Create a Quarkus REST CRUD Application with Hibernate Panache and REST Data Panache - Tutorial

Learn how to create a robust REST CRUD application in Quarkus using Hibernate Panache and REST Data Panache. #Quarkus #HibernatePanache #RESTDataPanache

Build React Frontend for Quarkus Application Using WildFly and Java 17

Learn how to consume Rest Services from a Quarkus application in a React front-end. Get started with NPM, create-react-app, and design a simple endpoint.