How to generate a JAX-RS CRUD application in Quarkus using Panache

In this tutorial we will learn how to generate automatically a JAX-RS CRUD application in Quarkus, starting from a Hibernate Panache Entity.

Creating CRUD applications for simple REST endpoint is a tedious task which requires adding lots of boilerplate code. Thanks to the quarkus-hibernate-orm-panache extension, this can be self-generated starting from a plain Entity class using or not a Respository class for persistence.

Please note that this feature is still experimental and currently supports Hibernate ORM with Panache and can generate CRUD resources that work with application/json and application/hal+json content.

First, check this tutorial for some background on Hibernate Panache: Managing Data Persistence with Quarkus and Hibernate Panache

Setting up the Quarkus project

We will now re-create the same application using self-generation of Resource Endpoints. Start by creating a new Quarkus project:

mvn io.quarkus:quarkus-maven-plugin:1.6.1.Final:create \
     -DprojectGroupId=com.mastertheboss \
     -DprojectArtifactId=panache-demo \
     -DclassName="com.mastertheboss.MyService" \
     -Dpath="/tickets" \
     -Dextensions="quarkus-hibernate-orm-rest-data-panache,quarkus-hibernate-orm-panache,quarkus-jdbc-postgresql,quarkus-resteasy-jsonb"

As you can see, our pom.xml file includes an extra dependency to support JAX-RS self-generation with Panache:

<dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-hibernate-orm-rest-data-panache</artifactId>
</dependency>

We will be adding a simple Entity objects, named Tickets which is unchanged from our first Panache example:

import javax.persistence.Column;
import javax.persistence.Entity;

import io.quarkus.hibernate.orm.panache.PanacheEntity;

@Entity
public class Ticket extends PanacheEntity {

    @Column(length = 20, unique = true)
    public String name;

    @Column(length = 3, unique = true)
    public String seat;

    public Ticket() {
    }

    public Ticket(String name, String seat) {
        this.name = name;
        this.seat = seat;
    }
}

Then, you have two options here. If you are updating the Entity directly from the JAX-RS Resource, then just add an interface which extends PanacheEntityResource which is typed with the Entity and the Primary Key used behind the hoods by Panache (in our example, we default to Long)

import io.quarkus.hibernate.orm.rest.data.panache.PanacheEntityResource;

public interface MyService  extends PanacheEntityResource<Ticket, Long> {
}

On the other hand, if your application uses a Repository Class, then you will use a different set of Types which includes also the TicketRepository class:

public interface MyService extends PanacheRepositoryResource<TicketRepository, Ticket, Long> {
}

That’s all! You just add one typed interface and you are done with the JAX-RS Resource! As a proof of concept, let’s add openapi extension to our project so that we can eventually check all endpoints which have been created:

    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-smallrye-openapi</artifactId>
    </dependency>

Since our application will run against PostgreSQL, in our application.properties includes the JDBC settings for connecting to a local instance of it:

quarkus.datasource.url=jdbc:postgresql:quarkusdb
quarkus.datasource.driver=org.postgresql.Driver
quarkus.datasource.username=quarkus
quarkus.datasource.password=quarkus
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.log.sql=true

Now start PostgreSQL, for example using Docker:

docker run --ulimit memlock=-1:-1 -it --rm=true --memory-swappiness=0 --name quarkus_test -e POSTGRES_USER=quarkus -e POSTGRES_PASSWORD=quarkus -e POSTGRES_DB=quarkusdb -p 5432:5432 postgres:10.5

We also have included an import.sql file to create some sample records:

INSERT INTO ticket(id, name,seat) VALUES (nextval('hibernate_sequence'), 'Phantom of the Opera','11A');
INSERT INTO ticket(id, name,seat) VALUES (nextval('hibernate_sequence'), 'Chorus Line','5B');
INSERT INTO ticket(id, name,seat) VALUES (nextval('hibernate_sequence'), 'Mamma mia','21A');

And the following class will run a minimal Test Case:

import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.containsString;

@QuarkusTest
public class MyServiceTest {

    @Test
    public void testListAllTickets() {

        given()
                .when().get("/my-service")
                .then()
                .statusCode(200)
                .body(
                        containsString("Phantom of the Opera"),
                        containsString("Chorus Line"),
                        containsString("Mamma mia")
                );

    }

}

Testing the application

Now if you run the application using:

mvn install quarkus:dev

You will see the following endpoints are available at: http://localhost:8080/openapi

openapi: 3.0.1
info:
  title: Generated API
  version: "1.0"
paths:
  /my-service:
    get:
      responses:
        "200":
          description: OK
    post:
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Ticket'
      responses:
        "200":
          description: OK
  /my-service/{id}:
    get:
      parameters:
      - name: id
        in: path
        required: true
        schema:
          format: int64
          type: integer
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Ticket'
    put:
      parameters:
      - name: id
        in: path
        required: true
        schema:
          format: int64
          type: integer
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Ticket'
      responses:
        "200":
          description: OK
    delete:
      parameters:
      - name: id
        in: path
        required: true
        schema:
          format: int64
          type: integer
      responses:
        "204":
          description: No Content
components:
  schemas:
    Ticket:
      type: object
      properties:
        id:
          format: int64
          type: integer
        name:
          type: string
        seat:
          type: string

As you can see, our JAX-RS Service has been bound with the hypened name of the Backing resource: “my-service”. As a proof of concept, you can for example query the list of Ticket objects:

curl http://localhost:8080/my-service

That will return:

[
   {
      "id":2,
      "name":"Chorus Line",
      "seat":"5B"
   },
   {
      "id":3,
      "name":"Mamma mia",
      "seat":"21A"
   },
   {
      "id":1,
      "name":"Phantom of the Opera",
      "seat":"11A"
   }
]

That’s all. We have demonstrated how to scaffold a CRUD JAX-RS application from an Entity object using Quarkus and Hibernate ORM Panache. Stay tuned for more updates on this matter: https://quarkus.io/guides/rest-data-panache

Source code for this tutorial: https://github.com/fmarchioni/mastertheboss/tree/master/quarkus/panache-rest-demo