1. Introduction

Quarkus is a popular Java framework optimized for creating applications with minimal memory footprint and fast startup times.

When paired with MongoDB, a popular NoSQL database, Quarkus provides a powerful toolkit for developing high-performance, scalable applications.

In this tutorial, we’ll explore configuring MongoDB with Quarkus, implementing basic CRUD operations, and simplifying these operations using Panache, Quarkus’s Object Document Mapper (ODM).

2. Configuration

2.1. Maven Dependency

To use MongoDB with Quarkus, we need to include the quarkus-mongodb-client dependency:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-mongodb-client</artifactId>
    <version>3.13.0</version>
</dependency>

This dependency provides the necessary tools to interact with a MongoDB database using the MongoDB Java client.

2.2. Running a MongoDB Database

For this article, we’ll run MongoDB in a Docker container. This is a convenient way to set up a MongoDB instance without having to install MongoDB directly on our machines.

We’ll start by pulling the MongoDB image from Docker Hub:

docker pull mongo:latest

And starting a new container:

docker run --name mongodb -d -p 27017:27017 mongo:latest

2.3. Configuring the MongoDB Database

The main property to configure is the URL to access MongoDB. We can include almost all of the configurations in the connection string.

We can configure the MongoDB client for a replica set of multiple nodes, but in our example, we’ll use a single instance on localhost:

quarkus.mongodb.connection-string = mongodb://localhost:27017

3. Basic CRUD Operations

Now that we have our database and project ready and connected let’s implement basic CRUD (Create, Read, Update, Delete) operations using the default Mongo client provided by Quarkus.

3.1. Defining the Entity

In this section, we will define the Article entity, representing a document in our MongoDB collection:

public class Article {
    @BsonId
    public ObjectId id;
    public String author;
    public String title;
    public String description;
    
    // getters and setters 
}

Our class includes fields for id, author, title, and description. ObjectId is a BSON (binary representation of JSON) type used as the default identifier for MongoDB documents. The @BsonId annotation designates a field as a MongoDB document’s identifier (_id).

When applied to a field, it indicates that this field should be mapped to the _id field in the MongoDB collection. Using this combination, we ensure that each Article document has a unique identifier that MongoDB can use to index and retrieve documents efficiently.

3.2. Defining the Repository

In this section, we’ll create the ArticleRepository class, using MongoClient to perform CRUD operations on the Article entity. This class will manage the connection to the MongoDB database and provide methods to create, read, update, and delete Article documents.

First, we’ll use dependency injection to get an instance of MongoClient:

@Inject
MongoClient mongoClient;

This allows us to interact with the MongoDB database without manually managing the connection.

We define a helper method getCollection() to get the articles collection from the articles database:

private MongoCollection<Article> getCollection() {
    return mongoClient.getDatabase("articles").getCollection("articles", Article.class);
}

Now, we can use the collection provider to perform basic CRUD operations:

public void create(Article article) {
    getCollection().insertOne(article);
}

The create() method inserts a new Article document into the MongoDB collection. This method uses insertOne() to add the provided article object, ensuring it is stored as a new entry in the articles collection.

public List<Article> listAll() {
    return getCollection().find().into(new ArrayList<>());
}

The listAll() method retrieves all Article documents from the MongoDB collection. It leverages the find() method to query all documents and collect them. We can also specify the type of collection that we want to return.

public void update(Article article) {
    getCollection().replaceOne(new org.bson.Document("_id", article.id), article);
}

The update() method replaces an existing Article document with the provided object. It uses replaceOne() to find the document with the matching _id and updates it with the new data.

public void delete(String id) {
    getCollection().deleteOne(new org.bson.Document("_id", new ObjectId(id)));
}

The delete() method removes an Article document from the collection by its id. It constructs a filter to match the _id and uses deleteOne to remove the first document that matches this filter.

3.3. Defining the Resource

As a brief example, we’ll limit ourselves to defining the resource and the repository without currently implementing the service layer.

Now, all we need to do is create our resource, inject the repository, and create a method for every operation:

@Path("/articles")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class ArticleResource {
    @Inject
    ArticleRepository articleRepository;

    @POST
    public Response create(Article article) {
        articleRepository.create(article);
        return Response.status(Response.Status.CREATED).build();
    }

    @GET
    public List<Article> listAll() {
        return articleRepository.listAll();
    }

    @PUT
    public Response update(Article updatedArticle) {
        articleRepository.update(updatedArticle);
        return Response.noContent().build();
    }

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

3.4. Testing our API

To ensure our API is working correctly, we can use curl, a versatile command-line tool for transferring data using various network protocols.

We’ll add a new article to the database. We use the HTTP POST method to send a JSON payload representing the article to the /articles endpoint:

curl -X POST http://localhost:8080/articles \
-H "Content-Type: application/json" \
-d '{"author":"John Doe","title":"Introduction to Quarkus","description":"A comprehensive guide to the Quarkus framework."}'

To verify that our article was successfully stored, we can use the HTTP GET method to fetch all articles from the database:

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

By running this we’ll get a JSON array containing all the articles currently stored in the database:

[
  {
    "id": "66a8c65e8bd3a01e0a509f0a",
    "author": "John Doe",
    "title": "Introduction to Quarkus",
    "description": "A comprehensive guide to Quarkus framework."
  }
]

4. Using Panache with MongoDB

Quarkus provides an additional layer of abstraction called Panache, which simplifies database operations and reduces boilerplate code. With Panache, we can focus more on our business logic and less on the data access code. Let’s see how we can implement the same CRUD operations using Panache.

4.1. Maven Dependency

To use Panache with MongoDB, we need to add the quarkus-mongodb-panache dependency:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-mongodb-panache</artifactId>
    <version>3.13.0</version>
</dependency>

4.2. Defining the Entity

When using Panache, our entity class will extend PanacheMongoEntity, which provides built-in methods for common database operations. We’ll also use the @MongoEntity annotation to define our MongoDB collection:

@MongoEntity(collection = "articles", database = "articles")
public class Article extends PanacheMongoEntityBase {
    private ObjectId id;
    private String author;
    private String title;
    private String description;

    // getters and setters
}

4.3. Defining the Repository

With Panache, we create a repository by extending the PanacheMongoRepository. This provides us with CRUD operations without writing boilerplate code:

@ApplicationScoped
public class ArticleRepository implements PanacheMongoRepository<Article> {}

When extending the PanacheMongoRepository class, several commonly used methods become available for performing CRUD operations and managing MongoDB entities. Now, we can use methods like persist(), listAll(), or findById out of the box.

4.4. Defining the Resource

Now, all we need to do is create our new resource that will use the new repository without all the boilerplate code:

@Path("/v2/articles")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class ArticleResource 
    @Inject
    ArticleRepository articleRepository;

    @POST
    public Response create(Article article) {
        articleRepository.persist(article);
        return Response.status(Response.Status.CREATED).build();
    }

    @GET
    public List<Article> listAll() {
        return articleRepository.listAll();
    }

    @PUT
    public Response update(Article updatedArticle) {
        articleRepository.update(updatedArticle);
        return Response.noContent().build();
    }

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

4.5. Testing API

We can also test the new api, using the same curl commands as in the other example. The only thing we are changing will be the called endpont, this time calling /v2/articles API. We’ll create the new article:

curl -X POST http://localhost:8080/v2/articles \
-H "Content-Type: application/json" \
-d '{"author":"John Doe","title":"Introduction to MongoDB","description":"A comprehensive guide to MongoDB."}'

And we’ll retrieve the existing articles:

curl -X GET http://localhost:8080/v2/articles

5. Conclusion

In this article, we’ve explored how to integrate MongoDB with Quarkus. We configured MongoDB and ran it within a Docker container, setting up a robust and scalable environment for our application. We demonstrated the implementation of CRUD operations using the default Mongo client.

Furthermore, we introduced Panache, Quarkus’s Object Document Mapper (ODM), which significantly simplifies data access by reducing boilerplate code.

As usual, the code for the application is available over on GitHub.