Creating simple CRUD REST APIs using Spring Boot and MongoDB
Tags: springboot, mongodb
Published: 2023-05-10 15:03:41

Hi everyone. In this new post, we will be creating simple CRUD REST APIs using Spring boot and MongoDB. Before we go into the code let's look at the topics we will be covering:

- Importing the dependencies needed for this project
- Connecting to our Mongo database using application.properties file
- Creating an entity which will contain all the column names
- Creating a repository and extending the MongoRepository class which contains the methods for CRUD operation
- Creating a Data transfer object which will hold the response after a certain event
- Creating a service class which will contain the logic for the apis
- Creating a controller which will handle our POST/GET/PUT/DELETE requests.
- Testing the apis we have created

## Importing the dependencies needed for this project
Let's add the dependency needed for MongoDB:

```xml
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
```
And to automatically generate the getter and setter methods for our entity we will be using the lombok dependency:

```xml
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<version>1.18.26</version>
	<scope>provided</scope>
</dependency>
```

## Connecting to our Mongo database using application.properties file
```properties
spring.data.mongodb.uri=<mongo cluster uri>
spring.data.mongodb.database=<database to be connected to>
```

## Creating an entity which will contain all the column names

Now we have to create an entity class which will contain all the columns that we need in our collection. Here is the code for the entity class:

```Java
package com.example.bookstore.entity;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

import java.time.LocalDateTime;

@Data
@Document("post")
public class Post {
    @Id
    private String id;

    private String title;

    private String body;

    private String author;

    private LocalDateTime createdAt;

    private LocalDateTime updatedAt;
}

```
Let's understand the annotations used here:

- `@Data` - generates all the boilerplate that is normally associated with simple POJOs (Plain Old Java Objects) and beans.
- `@Document` - used to identify a domain object, which is persisted to MongoDB.
- `@Id` - defines the primary key

## Creating a repository and extending the MongoRepository class which contains the methods for CRUD operation

Now we have to create a repository which will be extending the MongoRepository class. A repository is a mechanism for encapsulating storage, retrieval, and search behaviour of objects.

The MongoRepository extends `PagingAndSortingRepository` and `QueryByExampleExecutor` interfaces that further extend the `CrudRepository` interface. The code for the repository is like this:

```Java
package com.example.bookstore.repository;

import com.example.bookstore.entity.Post;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.stereotype.Repository;

@Repository
public interface PostRepository extends MongoRepository<Post, String> {
}

```
Let's understand the annotation used here:
- `@Repository` - signifies that the class is a DAO class. DAO class is where we write methods to perform operations over db.

## Creating a Data transfer object which will hold the response after a certain event

Now we create a Data transfer object(DTO) which will hold the response. Here is the code for the DTO:

```Java
package com.example.bookstore.dto;

import com.example.bookstore.entity.Post;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;

import java.util.List;

@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Response {
    @JsonProperty("response")
    private ResponseDetailsDto response;
}

```
We will be adding some more attributes to this DTO object. 
The `ResponseDetailsDto` class looks something like this:

```Java
package com.example.bookstore.dto;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;

@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseDetailsDto {
    @JsonProperty("responseCode")
    private String responseCode;

    @JsonProperty("responseStatus")
    private String responseStatus;

    @JsonProperty("responseMessage")
    private String responseMessage;
}
```
Let's understand the annotations used here:

- `@JsonInclude(JsonInclude.Include.NON_NULL)` - ignores null fields in an object. 
- `@JsonProperty` - tells Jackson ObjectMapper to map the JSON property name to the annotated java field's name.

## Creating a service class which will contain the logic for the apis

Now we will have to create a service class which will contain the logic for the application. First we need to create an interface which will have the return types (which in our case here is the Response DTO) and the method names. This is the code for the interface:

```Java
package com.example.bookstore.service;

import com.example.bookstore.dto.Response;
import com.example.bookstore.entity.Post;

public interface PostService {
    Response addPost(Post post);

    Response editPost(String postId, Post post);

    Response deletePost(String postId);

    Response getAllPosts();

    Response getPostById(String postId);
}

```
And here is the code for the service class which implements the methods defined in the interface: 

```Java
package com.example.bookstore.service;

import com.example.bookstore.dto.Response;
import com.example.bookstore.dto.ResponseDetailsDto;
import com.example.bookstore.entity.Post;
import com.example.bookstore.repository.PostRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.time.LocalDateTime;
import java.util.List;

@Service
public class PostServiceImpl implements PostService {

    @Autowired
    PostRepository postRepository;

    /**
     * Method to add post
     * @param post contents of the post
     * @return Response
     */
    @Override
    public Response addPost(Post post) {
        Response dto = new Response();
        ResponseDetailsDto res = new ResponseDetailsDto();
        Post newPost = new Post();

        newPost.setTitle(post.getTitle());
        newPost.setBody(post.getBody());
        newPost.setAuthor(post.getAuthor());
        newPost.setCreatedAt(LocalDateTime.now());

        newPost = postRepository.save(newPost);

        if(!ObjectUtils.isEmpty(newPost)) {
            res.setResponseCode("200");
            res.setResponseStatus("Success");
            res.setResponseMessage("New post added");
            dto.setResponse(res);
        } else {
            res.setResponseCode("400");
            res.setResponseStatus("Failed");
            res.setResponseMessage("Unable to add new post");
            dto.setResponse(res);
        }

        return dto;
    }

    /**
     * Method to edit post
     * @param postId id of the post
     * @param post update contents of the post
     * @return Response
     */
    @Override
    public Response editPost(String postId, Post post) {
        Response dto = new Response();
        ResponseDetailsDto res = new ResponseDetailsDto();
        Post postDetails = postRepository.findByPostId(postId);

        if(!ObjectUtils.isEmpty(postDetails)) {
            postDetails.setTitle(post.getTitle());
            postDetails.setBody(post.getBody());
            postDetails.setAuthor(post.getAuthor());
            postDetails.setUpdatedAt(LocalDateTime.now());

            postDetails = postRepository.save(postDetails);

            res.setResponseCode("200");
            res.setResponseStatus("Success");
            res.setResponseMessage("Post updated");
            dto.setResponse(res);
        } else {
            res.setResponseCode("400");
            res.setResponseStatus("Failed");
            res.setResponseMessage("No such post exists");
            dto.setResponse(res);
        }

        return dto;
    }

    /**
     * Method to delete a post
     * @param postId id of the post
     * @return Response
     */
    @Override
    public Response deletePost(String postId) {
        Response dto = new Response();
        ResponseDetailsDto res = new ResponseDetailsDto();
        Post postDetails = postRepository.findByPostId(postId);

        if(!ObjectUtils.isEmpty(postDetails)) {
            // deleting post from collection
            postRepository.deleteById(postId);

            res.setResponseCode("200");
            res.setResponseStatus("Success");
            res.setResponseMessage("Post deleted");
            dto.setResponse(res);
        } else {
            res.setResponseCode("400");
            res.setResponseStatus("Failed");
            res.setResponseMessage("Unable to fetch post details");
            dto.setResponse(res);
        }

        return dto;
    }

    /**
     * Method to get all the posts
     * @return Response
     */
    @Override
    public Response getAllPosts() {
        Response dto = new Response();
        ResponseDetailsDto res = new ResponseDetailsDto();
        List<Post> allPosts = postRepository.findAll();

        if(allPosts.size() > 0) {
            res.setResponseCode("200");
            res.setResponseStatus("Success");
            res.setResponseMessage("All posts fetched");
            dto.setResponse(res);
            dto.setPostList(allPosts);
        } else {
            res.setResponseCode("400");
            res.setResponseStatus("Failed");
            res.setResponseMessage("No posts added yet!");
            dto.setResponse(res);
        }
        return dto;
    }

    /**
     * Method to get post by id
     * @param postId - id of the post
     * @return Response
     */
    @Override
    public Response getPostById(String postId) {
        Response dto = new Response();
        ResponseDetailsDto res = new ResponseDetailsDto();
        Post postDetails = postRepository.findByPostId(postId);

        if(!ObjectUtils.isEmpty(postDetails)) {
            res.setResponseCode("200");
            res.setResponseStatus("Success");
            res.setResponseMessage("Post fetched!");
            dto.setResponse(res);
            dto.setPost(postDetails);
        } else {
            res.setResponseCode("400");
            res.setResponseStatus("Failed");
            res.setResponseMessage("No such post with the id provided");
            dto.setResponse(res);
        }

        return dto;
    }
}

```
We have five methods defined here:
- `addPost()` - does an insert operation in the post collection
- `editPost()` - does an update operation for the post id that has been passed to the method
- `deletePost()` - deletes the post
- `getAllPosts()` - fetches all the posts
- `getPostById()` - fetches post by the id

We also need to define a method to find post by id in our PostRepository class. This is the code change that is made in PostRepository:

```Java
@Repository
public interface PostRepository extends MongoRepository<Post, String> {
    @Query("{_id: '?0'}")
    Post findByPostId(String id);
}
```

And we need to add some attributes in the Response DTO. The code change for the Response DTO looks something like this:

```Java
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Response {
    @JsonProperty("response")
    private ResponseDetailsDto response;

    @JsonProperty("postList")
    private List<Post> postList;

    @JsonProperty("postDetails")
    private Post post;
}
```

Let's understand the annotations used in the service class:

- `@Service` - used to indicate that the class is a service class which contains the business logic.
- `@Autowired` - used to auto-wire particular property in a bean.
- `@Override` - means that the child class method overrides the base class method.

## Creating a controller which will handle our POST/GET/PUT/DELETE requests.

Now we have to create the controller which will handle our POST/GET/PUT/DELETE requests. This is the code for the controller class:

```Java
package com.example.bookstore.controller;

import com.example.bookstore.dto.Response;
import com.example.bookstore.entity.Post;
import com.example.bookstore.service.PostService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping(value = "/post", produces = "application/json")
public class PostController {

    @Autowired
    PostService postService;

    @PostMapping("/add-post")
    Response addPost(@RequestBody Post post) {
        Response res = postService.addPost(post);
        return res;
    }

    @PostMapping("/edit-post/{postId}")
    Response editPost(@PathVariable("postId") String postId, @RequestBody Post post) {
        Response res = postService.editPost(postId, post);
        return res;
    }

    @DeleteMapping("/delete-post/{postId}")
    Response deletePost(@PathVariable("postId") String postId) {
        Response res = postService.deletePost(postId);
        return res;
    }

    @GetMapping("/get-all-posts")
    Response getAllPosts() {
        Response res = postService.getAllPosts();
        return res;
    }

    @GetMapping("/get-post-by-id/{postId}")
    Response getPostById(@PathVariable("postId") String postId) {
        Response res = postService.getPostById(postId);
        return res;
    }

}

```
Let's understand the annotations we have used in our controller class:

- `@RestController` - mainly used for building restful web services using Spring MVC. The class annotated with this annotation returns JSON response in all the methods.
- `@RequestMapping` - used to map web requests onto specific handler class and/or handler methods.
- `@PostMapping` - maps HTTP POST requests onto specific handler methods
- `@GetMapping` - maps HTTP GET requests onto specific handler methods
- `@DeleteMapping` - maps HTTP DELETE requests onto specific handler methods

## Testing the apis we have created

The last step is to test the apis we have created, I will be using insomnia to test the apis. Here are the output of the apis we created:

1. Add post


![Add post request](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j4va7io1yj0njr7b7884.png)

2. Edit post

Here we are passing the ObjectId of the record that got added into the collection as an argument to the url.

![Edit post request](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iuhty2a6dhgqwrc7bwrj.png)

3. Get all posts


![Get all posts request](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iknam204pbqvnq36b03n.png)

4. Get post by id

![Get post by id request](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qqfwhzo95sjs5wj9k9pu.png)

5. Delete post


![Delete post request](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rne5bym0cuiiccgwuupr.png)

So that is how we can create RESTful apis using Springboot and MongoDB. I hope you liked this post and it was helpful for you. Please let me know if you have any questions. Cheers!