Skip to main content

One post tagged with "Java"

View All Tags

Building RESTful API with Quarkus, and PostgreSQL

· 5 min read
Huseyin BABAL
Software Developer

Introduction

Quarkus is a modern Java framework designed for developers who want to build cloud-native applications with ease. Unlike traditional Java frameworks, Quarkus is optimized for fast startup times, low memory usage, and seamless integration with tools like Kubernetes and GraalVM. Whether you’re building microservices, serverless functions, or APIs, Quarkus helps you write Java code that runs efficiently in resource-constrained environments.

In this guide, we’ll use Quarkus to create a RESTful API for managing tasks. Along the way, we’ll see how Quarkus simplifies database access with Hibernate ORM and Panache, while offering developer-friendly features like live reload and built-in dependency injection.

Persistence Layer

In this article, we will be using PostgreSQL as our database. You can maintain your database in any database management system. For a convenient deployment option, consider cloud-based solutions like Rapidapp, which offers managed PostgreSQL databases, simplifying setup and maintenance.

tip

Create a free database in Rapidapp in seconds here

Step-by-Step Guide: Creating a Todo REST API

Project Setup and Dependencies

To begin, we’ll create a new Quarkus project using the Maven plugin. Run the following command:

mvn io.quarkus:quarkus-maven-plugin:create \
-DprojectGroupId=com.example \
-DprojectArtifactId=quarkus-todo-api \
-Dextensions="resteasy, hibernate-orm-panache, jdbc-postgresql"

This command generates a project structure with RESTEasy for building APIs, Hibernate ORM with Panache for database access, and PostgreSQL JDBC driver for database connectivity. Navigate to the project directory:

cd quarkus-todo-api

Configuring the Database

Edit src/main/resources/application.properties to include your database connection settings:

src/main/resources/application.properties
quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=<username>
quarkus.datasource.password=<password>

quarkus.datasource.jdbc.url=jdbc:postgresql://<host>:<port>/<database>
quarkus.hibernate-orm.database.generation = update

Line 6: This will generate relations in PostgreSQL database by using entities.

Creating the Task Entity

Entities represent database tables in your application. Let’s define a Task entity:

package io.rapidapp;

import io.quarkus.hibernate.orm.panache.PanacheEntity;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Data;

@Entity
@Data
public class Task {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private boolean completed;

}

Hibernate ORM will map this entity to a table called Task.

Be sure you have Lombok dependency in your pom.xml file.

Creating a Repository

With Quarkus and Panache, repositories are straightforward. Create TaskRepository to manage database operations:

package io.rapidapp;

import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class TaskRepository implements PanacheRepository<Task> {

}

With this definition, you will have basic crud operations on top of Task entity. This repository provides methods like findById, listAll, and persist out of the box.

Building the RESTful API

In this step, we’ll create endpoints to handle the CRUD operations for the Task entity. Each endpoint will represent a RESTful route that performs a specific action—such as creating, retrieving, updating, or deleting tasks in the database. These endpoints are defined in a resource class, following the standard JAX-RS (Jakarta RESTful Web Services) specification that Quarkus supports.

The TaskResource class will serve as the controller for our application. It interacts with the TaskRepository to manage database operations. By leveraging Quarkus’ dependency injection, we can keep the code clean and focused.

Here’s the implementation of the resource:

src/main/java/io/rapidapp/TaskResource.java
package io.rapidapp;

import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.List;

@Path("/tasks")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class TaskResource {

@Inject
TaskRepository taskRepository;

// Retrieve all tasks
@GET
public List<Task> list() {
return taskRepository.listAll();
}

// Retrieve a single task by ID
@GET
@Path("/{id}")
public Response get(@PathParam("id") Long id) {
Task task = taskRepository.findById(id);
if (task == null) {
return Response.status(Response.Status.NOT_FOUND).build();
}
return Response.ok(task).build();
}

// Create a new task
@POST
@Transactional
public Response create(Task task) {
taskRepository.persist(task);
return Response.status(Response.Status.CREATED).entity(task).build();
}

// Update an existing task
@PUT
@Path("/{id}")
@Transactional
public Response update(@PathParam("id") Long id, Task updatedTask) {
Task existingTask = taskRepository.findById(id);
if (existingTask == null) {
return Response.status(Response.Status.NOT_FOUND).build();
}
existingTask.setTitle(updatedTask.getTitle());
existingTask.setCompleted(updatedTask.isCompleted());
return Response.ok(existingTask).build();
}

// Delete a task by ID
@DELETE
@Path("/{id}")
@Transactional
public Response delete(@PathParam("id") Long id) {
boolean deleted = taskRepository.deleteById(id);
if (!deleted) {
return Response.status(Response.Status.NOT_FOUND).build();
}
return Response.noContent().build();
}
}
  • Dependency Injection: The @Inject annotation is used to inject the TaskRepository into the TaskResource class. This allows the resource to interact with the database without needing to manage the repository lifecycle.
  • Path Annotations: The @Path annotation defines the route for each endpoint. For example, /tasks corresponds to the list and creation endpoints, while /tasks/{id} handles operations for a specific task.
  • Response Handling: Each method returns a Response object that represents the HTTP response. This includes status codes like 200 OK, 201 Created, or 404 Not Found, and any data payloads.
  • Transactional Methods: Methods that modify the database (POST, PUT, DELETE) are marked with @Transactional. This ensures that changes are committed to the database only if the method completes successfully.

By using JAX-RS annotations and Quarkus features like Panache and dependency injection, we can build a RESTful API that’s both powerful and easy to maintain. This structure also keeps our codebase clean and modular, which is crucial for scaling the application in the future.

Running the Application

Start the Quarkus development server:

./mvnw quarkus:dev

Your API will be accessible at http://localhost:8080/tasks.

Demo

Create a new task

curl -X POST http://localhost:8080/tasks \
-H "Content-Type: application/json" \
-d '{
"title": "Finish blog post",
"completed": false
}'

Retrieve all tasks

curl -X GET http://localhost:8080/tasks

Get a task by ID

curl -X GET http://localhost:8080/tasks/1

Update a task

curl -X PUT http://localhost:8080/tasks/1 \
-H "Content-Type: application/json" \
-d '{
"completed": true
}'

Delete a task

curl -X DELETE http://localhost:8080/tasks/1

Conclusion

With Quarkus, you can build performant, production-ready RESTful APIs with minimal boilerplate. In this guide, we implemented a simple API for managing tasks, covering entity creation, database interaction with Panache, and CRUD endpoints. Quarkus’ developer-friendly features like hot reload and simplified ORM make it an excellent choice for modern Java development.

tip

You can find the complete source code for this project on GitHub.