Introduction to Data Pagination With Quarkus and MongoDB: A Comprehensive Tutorial
Otavio Santana7 min read • Published Apr 25, 2024 • Updated Apr 25, 2024
FULL APPLICATION
Rate this tutorial
In modern web development, managing large datasets efficiently through APIs is crucial for enhancing application
performance and user experience. This tutorial explores pagination techniques using Quarkus and MongoDB, a robust
combination for scalable data delivery. Through a live coding session, we'll delve into different pagination methods and
demonstrate how to implement these in a Quarkus-connected MongoDB environment. This guide empowers developers to
optimize REST APIs for effective data handling.
1 git clone git@github.com:mongodb-developer/quarkus-pagination-sample.git
For this tutorial, you'll need:
- Java 21.
- Maven.
- A MongoDB cluster.
- MongoDB Atlas (Option 1)
- Docker (Option 2)
You can use the following Docker command to start a standalone MongoDB instance:
1 docker run --rm -d --name mongodb-instance -p 27017:27017 mongo
Or you can use MongoDB Atlas and try the M0 free tier to deploy your cluster.
- Configure your project by selecting the desired options, such as the group and artifact ID.
- Add the necessary dependencies to your project. For this tutorial, we will add:
- JNoSQL Document MongoDB [quarkus-jnosql-document-mongodb].
- RESTEasy Reactive [quarkus-resteasy-reactive].
- RESTEasy Reactive Jackson [quarkus-resteasy-reactive-jackson].
- OpenAPI [quarkus-smallrye-openapi].
Note: If you cannot find some dependencies, you can add them manually in the
pom.xml
. See the file below.- Generate the project, download the ZIP file, and extract it to your preferred location. Remember that the file structure may vary with different Quarkus versions, but this should be fine for the tutorial. The core focus will be modifying the
pom.xml
file and source code, which remains relatively consistent across versions. Any minor structural differences should be good for your progress, and you can refer to version-specific documentation if needed for a seamless learning experience.
At this point, your pom.xml file should look like this:
1 <dependencies> 2 <dependency> 3 <groupId>io.quarkus</groupId> 4 <artifactId>quarkus-smallrye-openapi</artifactId> 5 </dependency> 6 <dependency> 7 <groupId>io.quarkiverse.jnosql</groupId> 8 <artifactId>quarkus-jnosql-document-mongodb</artifactId> 9 <version>3.3.0</version> 10 </dependency> 11 <dependency> 12 <groupId>io.quarkus</groupId> 13 <artifactId>quarkus-resteasy</artifactId> 14 </dependency> 15 <dependency> 16 <groupId>io.quarkus</groupId> 17 <artifactId>quarkus-resteasy-jackson</artifactId> 18 </dependency> 19 <dependency> 20 <groupId>io.quarkus</groupId> 21 <artifactId>quarkus-arc</artifactId> 22 </dependency> 23 <dependency> 24 <groupId>io.quarkus</groupId> 25 <artifactId>quarkus-junit5</artifactId> 26 <scope>test</scope> 27 </dependency> 28 <dependency> 29 <groupId>io.rest-assured</groupId> 30 <artifactId>rest-assured</artifactId> 31 <scope>test</scope> 32 </dependency> 33 </dependencies>
We will work with the latest version of Quarkus alongside Eclipse JNoSQL Lite, a streamlined integration that notably
does not rely on reflection. This approach enhances performance and simplifies the configuration process, making it an
optimal choice for developers looking to maximize efficiency in their applications.
Before you dive into the implementation, it's essential to configure your MongoDB database properly. In MongoDB, you
must often set up credentials and specific configurations to connect to your database instance. Eclipse JNoSQL provides
a flexible configuration mechanism that allows you to manage these settings efficiently.
You can find detailed configurations and setups for various databases, including MongoDB, in the Eclipse JNoSQL GitHub
repository.
To run your application locally, you can configure the database name and properties in your application's
application.properties
file. Open this file and add the following line to set the database name:1 quarkus.mongodb.connection-string = mongodb://localhost 2 jnosql.document.database = fruits
This configuration will enable your application to:
- Use the "fruits" database.
- Connect to the MongoDB cluster available at the provided connection string.
In production, make sure to enable access control and enforce authentication. See the security checklist for more
details.
It's worth mentioning that Eclipse JNoSQL leverages Eclipse MicroProfile Configuration, which is designed to facilitate
the implementation of twelve-factor applications, especially in configuration management. It means you can override
properties through environment variables, allowing you to switch between different configurations for development,
testing, and production without modifying your code. This flexibility is a valuable aspect of building robust and easily
deployable applications.
Now that your database is configured, you can proceed with the tutorial and create your RESTful API with Quarkus and
Eclipse JNoSQL for MongoDB.
In this step, we will create a simple
Fruit
entity using Java records. Create a new class in the src/main/java
directory named Fruit
.1 import jakarta.nosql.Column; 2 import jakarta.nosql.Convert; 3 import jakarta.nosql.Entity; 4 import jakarta.nosql.Id; 5 import org.eclipse.jnosql.databases.mongodb.mapping.ObjectIdConverter; 6 7 8 public class Fruit { 9 10 11 12 private String id; 13 14 15 private String name; 16 17 public String getId() { 18 return id; 19 } 20 21 public void setId(String id) { 22 this.id = id; 23 } 24 25 public String getName() { 26 return name; 27 } 28 29 public void setName(String name) { 30 this.name = name; 31 } 32 33 34 public String toString() { 35 return "Fruit{" + 36 "id='" + id + '\'' + 37 ", name='" + name + '\'' + 38 '}'; 39 } 40 41 public static Fruit of(String name) { 42 Fruit fruit = new Fruit(); 43 fruit.setName(name); 44 return fruit; 45 } 46 47 }
We will simplify the integration between Java and MongoDB using the Jakarta Data repository by creating an interface
that extends NoSQLRepository. The framework automatically implements this interface, enabling us to define methods for
data retrieval that integrate seamlessly with MongoDB. We will focus on implementing two types of pagination: offset
pagination represented by
Page
and keyset (cursor) pagination represented by CursoredPage
.Here's how we define the FruitRepository interface to include methods for both pagination strategies:
1 import jakarta.data.Sort; 2 import jakarta.data.page.CursoredPage; 3 import jakarta.data.page.Page; 4 import jakarta.data.page.PageRequest; 5 import jakarta.data.repository.BasicRepository; 6 import jakarta.data.repository.Find; 7 import jakarta.data.repository.OrderBy; 8 import jakarta.data.repository.Repository; 9 10 11 public interface FruitRepository extends BasicRepository<Fruit, String> { 12 13 14 CursoredPage<Fruit> cursor(PageRequest pageRequest, Sort<Fruit> order); 15 16 17 18 Page<Fruit> offSet(PageRequest pageRequest); 19 20 long countBy(); 21 22 }
We'll demonstrate how to populate and manage the MongoDB database with a collection of fruit entries at the start of the
application using Quarkus. We'll ensure our database is initialized with predefined data, and we'll also handle cleanup
on application shutdown. Here's how we can structure the SetupDatabase class:
1 import jakarta.enterprise.context.ApplicationScoped; 2 3 import jakarta.enterprise.event.Observes; 4 5 import io.quarkus.runtime.ShutdownEvent; 6 import io.quarkus.runtime.StartupEvent; 7 import org.jboss.logging.Logger; 8 9 import java.util.List; 10 11 12 public class SetupDatabase { 13 14 private static final Logger LOGGER = Logger.getLogger(SetupDatabase.class.getName()); 15 16 private final FruitRepository fruitRepository; 17 18 public SetupDatabase(FruitRepository fruitRepository) { 19 this.fruitRepository = fruitRepository; 20 } 21 22 23 void onStart( StartupEvent ev) { 24 LOGGER.info("The application is starting..."); 25 long count = fruitRepository.countBy(); 26 if (count > 0) { 27 LOGGER.info("Database already populated"); 28 return; 29 } 30 List<Fruit> fruits = List.of( 31 Fruit.of("apple"), 32 Fruit.of("banana"), 33 Fruit.of("cherry"), 34 Fruit.of("date"), 35 Fruit.of("elderberry"), 36 Fruit.of("fig"), 37 Fruit.of("grape"), 38 Fruit.of("honeydew"), 39 Fruit.of("kiwi"), 40 Fruit.of("lemon") 41 ); 42 fruitRepository.saveAll(fruits); 43 } 44 45 void onStop( ShutdownEvent ev) { 46 LOGGER.info("The application is stopping..."); 47 fruitRepository.deleteAll(fruitRepository.findAll().toList()); 48 } 49 50 }
Now, let's create a RESTful API to manage developer records. Create a new class in
src/main/java
named FruitResource
.1 import jakarta.data.Sort; 2 import jakarta.data.page.PageRequest; 3 import jakarta.ws.rs.DefaultValue; 4 import jakarta.ws.rs.GET; 5 import jakarta.ws.rs.Path; 6 import jakarta.ws.rs.Produces; 7 import jakarta.ws.rs.QueryParam; 8 import jakarta.ws.rs.core.MediaType; 9 10 11 public class FruitResource { 12 13 private final FruitRepository fruitRepository; 14 15 private static final Sort<Fruit> ASC = Sort.asc("name"); 16 private static final Sort<Fruit> DESC = Sort.asc("name"); 17 18 public FruitResource(FruitRepository fruitRepository) { 19 this.fruitRepository = fruitRepository; 20 } 21 22 23 24 25 public Iterable<Fruit> hello( long page, 26 int size) { 27 var pageRequest = PageRequest.ofPage(page).size(size); 28 return fruitRepository.offSet(pageRequest).content(); 29 } 30 31 32 33 34 public Iterable<Fruit> cursor( String after, 35 String before, 36 int size) { 37 if (!after.isBlank()) { 38 var pageRequest = PageRequest.ofSize(size).afterCursor(PageRequest.Cursor.forKey(after)); 39 return fruitRepository.cursor(pageRequest, ASC).content(); 40 } else if (!before.isBlank()) { 41 var pageRequest = PageRequest.ofSize(size).beforeCursor(PageRequest.Cursor.forKey(before)); 42 return fruitRepository.cursor(pageRequest, DESC).stream().toList(); 43 } 44 var pageRequest = PageRequest.ofSize(size); 45 return fruitRepository.cursor(pageRequest, ASC).content(); 46 } 47 48 }
Now that we've created our RESTful API for managing developer records, it's time to put it to the test. We'll
demonstrate how to interact with the API using various HTTP requests and command-line tools.
1 ./mvnw compile quarkus:dev
We will use
curl
to learn more about pagination using the URLs provided. It is a command-line tool that is often used
to send HTTP requests. The URLs you have been given are used to access a REST API endpoint fetching fruit pages using
offset pagination. Each URL requests a different page, enabling us to observe how pagination functions via the API.
Below is how you can interact with these endpoints using the curl
tool.This command requests the first page of fruits from the server.
1 curl --location http://localhost:8080/fruits/offset?page=1
This command gets the next set of fruits, which is the second page.
1 curl --location http://localhost:8080/fruits/offset?page=2
By requesting the fifth page, you can see how the API responds when you request a page that might be beyond the range of
existing data.
1 curl --location http://localhost:8080/fruits/offset?page=5
To continue exploring cursor-based pagination with your API, using both
after
and before
parameters provides a way
to navigate through your dataset forward and backward respectively. This method allows for flexible data retrieval,
which can be particularly useful for interfaces that allow users to move to the next or previous set of results. Here's
how you can structure your curl
commands to use these parameters effectively:This command gets the first batch of fruits without specifying a cursor, starting from the beginning.
1 curl --location http://localhost:8080/fruits/cursor
This command fetches the list of fruits that appear after "banana" in your dataset. This is useful for moving forward in
the list.
1 curl --location http://localhost:8080/fruits/cursor?after=banana
This command is used to go back to the set of fruits that precede "date" in the dataset. This is particularly useful for
implementing "Previous" page functionality.
1 curl --location http://localhost:8080/fruits/cursor?before=date
This tutorial explored the fundamentals and implementation of pagination using Quarkus and MongoDB, demonstrating how to
manage large datasets in web applications effectively. By integrating the Jakarta Data repository with Quarkus, we
designed interfaces that streamline the interaction between Java and MongoDB, supporting offset and cursor-based
pagination techniques. We started by setting up a basic Quarkus application and configuring MongoDB connections. Then,
we demonstrated how to populate the database with initial data and ensure clean shutdown behavior.
Throughout this tutorial, we've engaged in live coding sessions, implementing and testing various pagination methods.
We've used the
curl
command to interact with the API, fetching data with no parameters, and using after
and before
parameters to navigate through the dataset forward and backward. The use of cursor-based pagination, in particular,
has showcased its benefits in scenarios where datasets are frequently updated or when precise data retrieval control is
needed. This approach not only boosts performance by avoiding the common issues of offset pagination but also provides a
user-friendly way to navigate through data.Top Comments in Forums
There are no comments on this article yet.