DEV Community

Cover image for How to create REST API in quarkus?
Dhruv garg
Dhruv garg

Posted on

How to create REST API in quarkus?

I have finally decided to post my first article and I am trying to learn quarkus. So here I am writing this article for our first supersonic Quarkus application. We will develop a RESTful application using JAX-RS for a To Do application.

In recent, I have heard many times that java is going to be obsolete and many reasons for this are valid. But, no more if java community can come up with something like quarkus then I am sure java is not going anywhere. It is really an awesome framework and hopefully you will also say that after reading this article.

What is Quarkus?

Quarkus is a Kubernetes Native Java framework tailored for GraalVM and HotSpot. The goal of Quarkus is to make Java a leading platform in Kubernetes and serverless environments while offering developers a unified reactive and imperative programming model to optimally address a wider range of distributed application architectures.

Why quarkus?

Whenever someone thinks about java development. They think that application will take huge memory and it will be hard to develop campared to it's counterparts. But quarkus, let's you develop high performance applications that take very low space and is easy to work with because of features like live reload. Also wide array of extensions and libraries provided by quarkus team and community makes building quarkus application much easier and fun (yeah java can be fun πŸ˜…).

please go to this link for more information: https://quarkus.io/

summary:

  • less memory usage
  • fast startup time
  • live reload out of the box
  • uses standard java ee specifications. so if you are already familier with jakarta ee or even with spring you are good to go
  • Quarkus does not use reflection API.

let's build a simple TO DO list endpoints

For setting up Development environment

you will need :

  • JDK 8 or 11 with JAVA_HOME configured
  • GraalVM (If you want to build native image)
  • Apache Maven or Gradle
  • IDE (I prefer intellij Idea)

Getting started

The easiest way is to go to https://code.quarkus.io/ and download zip file of initial application with default config and these libraries

  • RestEasy implementation of JAX-RS
  • RestEasy JsonB

It is your choice to use maven or gradle. I will be using gradle.

The project Structure will look like this:

quarkus-project/                    
β”œβ”€β”€ build.gradle
β”œβ”€β”€ gradle
β”‚   └── wrapper
β”‚       β”œβ”€β”€ gradle-wrapper.jar
β”‚       └── gradle-wrapper.properties
β”œβ”€β”€ gradle.properties
β”œβ”€β”€ gradlew
β”œβ”€β”€ gradlew.bat
β”œβ”€β”€ README.md
β”œβ”€β”€ settings.gradle
└── src
    β”œβ”€β”€ main
    β”‚   β”œβ”€β”€ docker
    β”‚   β”‚   β”œβ”€β”€ Dockerfile.jvm
    β”‚   β”‚   └── Dockerfile.native
    β”‚   β”œβ”€β”€ java
    β”‚   β”‚   └── org
    β”‚   β”‚       └── quarkus
    β”‚   β”‚           β”œβ”€β”€ ExampleResource.java
    β”‚   └── resources
    β”‚       β”œβ”€β”€ application.properties
    β”‚       └── META-INF
    β”‚           └── resources
    β”‚               └── index.html
    β”œβ”€β”€ native-test
    β”‚   └── java
    β”‚       └── org
    β”‚           └── quarkus
    β”‚               └── NativeToDoResourceIT.java
    └── test
        └── java
            └── org
                └── quarkus
                    └── ToDoResourceTest.java
Enter fullscreen mode Exit fullscreen mode
  • The ExampleResource.java contains the REST Endpoint.
  • The docker folder contains two files for building app as container. Dockerfile.jvm to run application on JVM and Dockerfile.native to build native app.
  • The test and native-test for testing JVM and native application
  • The static resources are put inside resources folder, like index.html.
  • The application.properties is the yaml file used for configuration. e.g. Defining datasource and configuring database.

Building our ToDo API

For simplicity I will not be using database in this article and maybe show how to use it in next article.

You can try to run initial project using

./gradlew quarkusDev
./mvnw quarkus:dev
Enter fullscreen mode Exit fullscreen mode

This will run app in dev mode which provides hot reload. so any change in code is instantly visible without restarting app. Go Ahead and try out this, by simply changing some text of return statement.

The initial project contain ExampleResource a simple hello end point that returns hello. Replace this with ToDoResource and add this code.

ToDoResource.java

This contains 6 EndPoints for basic CRUD operations:

  • GET /api/todo returns all tasks
  • GET /api/todo/{id} returns task with given id
  • POST /api/todo with ToDo json object body to create new task
  • POST /api/todo with task String body to create new task
  • PATCH /api/todo/status/{id} to mark task completed or non-completed
  • DELETE /api/todo/{id} deletes task with given id

@Path Annotation specifies uri for endpoint. It is used to define base uri when declared at top of resource and specifies uri for specific API if specified over function. e.g. api/todo in this case is base uri and status/{id} is uri of changeStatus API.

@Produces Annotation tells which type of result is served by API. for e.g. JSON, plain text or HTML.

@Consumes Annotation tells which type of body is consumed by API. for e.g. JSON, plain text or HTML.

@Inject Annotation is used to inject Instanse of ToDoService using CDI. This enables us to just use the dependency/object we need without worrying about it's creation and lifecycle as it is managed by context and dependency injection system.

package org.quarkus;

import javax.inject.Inject;
import javax.validation.Valid;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Path("/api/todo")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class ToDoResource {

    @Inject
    ToDoService service;

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response newTask(@Valid ToDo task) {
        try {
            if (task == null) {
                return Response.status(400).entity("Task not provided").build();
            }
            return Response.ok(service.addTask(task)).build();
        }
        catch (Exception e){
            return Response.serverError().build();
        }
    }

    @POST
    @Consumes(MediaType.TEXT_PLAIN)
    public Response newTaskString(@Valid String task) {
        try {
            if (task == null) {
                return Response.status(400).entity("Task not provided").build();
            }
            return Response.ok(service.addTask(task)).build();
        }
        catch (Exception e){
            return Response.serverError().build();
        }
    }

    @GET
    public Response allTasks(){
        return Response.ok(service.allTasks()).build();
    }

    @GET
    @Path("/{id}")
    public Response task(@PathParam("id") String id){
        return Response.ok(service.getTask(id)).build();
    }

    @PATCH
    @Path("status/{id}")
    public Response changeStatus(@PathParam("id") String id){
        return Response.ok(service.changeStatus(id)).build();
    }

    @DELETE
    @Path("/{id}")
    public Response delete(@PathParam("id") String id){
        return Response.ok(service.delete(id)).build();
    }
}


Enter fullscreen mode Exit fullscreen mode

ToDo.java

This is our POJO with constructors to initialize objects.

package org.quarkus;

import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.Future;
import javax.validation.constraints.NotNull;
import java.util.Date;
import java.util.UUID;

public class ToDo {
    @NotNull
    public String id;

    @NotNull
    public String task;

    @NotNull
    public boolean isComplete;

    @NotNull
    public Date time;

    public ToDo(String task, Date time) {
        this.id = UUID.randomUUID().toString();
        this.task = task;
        this.isComplete = false;
        this.time = time;
    }

    public ToDo(String task, boolean isComplete, Date time) {
        this.id = UUID.randomUUID().toString();
        this.task = task;
        this.isComplete = isComplete;
        this.time = time;
    }

    @Override
    public String toString() {
        return "ToDo{" +
                "id='" + id + '\'' +
                ", task='" + task + '\'' +
                ", isComplete=" + isComplete +
                ", time=" + time +
                '}';
    }
}

Enter fullscreen mode Exit fullscreen mode

TodoService.java

This class contains logic of application. It is ApplicationScoped which means only one instance of this class is created during lifetime of app and same instance is injected and reused wherever it is required by CDI system.

package org.quarkus;

import javax.enterprise.context.ApplicationScoped;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;

@ApplicationScoped
public class ToDoService {
    ArrayList<ToDo> list = new ArrayList<>();

    public ToDoService(){

    }

    public boolean addTask(String task){
        ToDo toDo = new ToDo(task, false, new Date());
        return list.add(toDo);
    }

    public boolean addTask(ToDo task){
        return list.add(task);
    }

    public List<ToDo> allTasks(){
        return list;
    }

    public ToDo getTask(String id){
        Optional<ToDo> item = list.stream().filter(toDo -> toDo.id.equals(id)).findFirst();
        return item.orElse(null);
    }

    public ToDo changeStatus(String id){
        ToDo todo = getTask(id);
        todo.isComplete = !todo.isComplete;
        return todo;
    }

    public boolean delete(String id){
        return list.remove(getTask(id));
    }
}

Enter fullscreen mode Exit fullscreen mode

Now you can run the application using same command mentioned earlier. The output on terminal will look like this.

dhruv@dhruv-laptop:~/Programs/javaPrograms/to_do$ ./gradlew quarkusDev
Starting a Gradle Daemon (subsequent builds will be faster)

> Task :quarkusDev
Listening for transport dt_socket at address: 5005
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2020-07-02 19:03:56,518 INFO  [io.quarkus] (Quarkus Main Thread) to_do 1.0.0-SNAPSHOT on JVM (powered by Quarkus 1.5.2.Final) started in 6.566s. Listening on: http://0.0.0.0:8080
2020-07-02 19:03:56,578 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2020-07-02 19:03:56,579 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, resteasy, resteasy-jsonb]
<=========----> 75% EXECUTING [41s]
> :quarkusDev

Enter fullscreen mode Exit fullscreen mode

If you look closely you will see that it is also telling all installed features like cdi, resteasy, resteasy-jsonb in 3rd line after quarkus banner.

Building the native executable

Use below command to generate a native executable

      ./mvnw package -Pnative
      ./gradlew build -Dquarkus.package.type=native
Enter fullscreen mode Exit fullscreen mode

The build will produce target/{package_name}-1.0-SNAPSHOT-runner. You can run it using: ./target/{package_name}-1.0-SNAPSHOT-runner on linux.

For more details on proper setup follow this

Conclusion

In this article we have covered, how to create simple REST Api in Quarkus and why quarkus enables us developers to build better applications.

You can find source code here. star repo if you like it.

Hope you liked this article and it helped you to learn something new. This is my first time writing on quarkus and posting Article/Blog online. Feel free to let me know if anything is wrong in comments, also tell in comments if you liked it.
You can connect with me on twitter or linkedin

Thanks for reading this article. Best of luck for learning Quarkus.

Top comments (0)