Building RESTful API with Quarkus, and PostgreSQL
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.
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:
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:
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 theTaskRepository
into theTaskResource
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.
You can find the complete source code for this project on GitHub.