BUILDING A SIMPLE FULL STACK PROJECT WITH SPRING BOOT, VUE.JS AND MONGODB (PART 1)

In this two-part series, I will demonstrate creating a simple ToDo application. I will be using the Spring Boot framework to prepare the backend project and I will be using Vue.js as for preparing the frontend. I shall use MongoDB as the database. In part 1, I will be showing the development procedure of the Spring Boot backend project. You can access the source code of the project in this repository.

SpringBoot ToDo Backend

This is a simple project demonstrate how to prepare a RESTful CRUD backend server with SpringBoot framework. In this project, we prepare a simple Todo model with few fields and expose API for read all instances, read single instance by id, create a new instance, update an instance by id and deleting an instance by id.

We have allowed CORS to accept API requests from different origin, which is useful for the frontend project.

We have used MongoDB as the database of this project.

Before you start

You will need Java Development Toolkit and MongoDB installed in your system to implement this project. Official Spring website (https://spring.io/) recomends AdoptOpenJDK. You will have detailed instruction to install and set up MongoDB in your system here.

Initializing the project

The project is initiated using Spring Initializr. Go to Spring Initializr and select the following options:

  • You can choose either of the Maven or Gradle project based on your liking. We have used Gradle here.
  • As language, we used Java.
  • We used Spring Boot version 2.3.1.
  • Used Java 8.
  • As dependency, we have used Spring Web and Spring Data MongoDB.
  • Provide project name and package name as your like.
  • Hit “Generate”.

Your project will be initiated and a Zip archive will be downloaded. You can unzip the package and work on it.

Project structure

The project initiated with Spring Initializr is instantly runnable. The project directory will contain the following files or directories:

  • src directory to accommodate your source codes.
    • main directory to accommodate your project source codes.
    • test directory to accommodate your test classes.
  • build.gradle will hold your build configurations.
  • gradlew / gradlew.bat to run the project depending on your OS.
  • settings.gradle to hold the settings for gradle.

You can run the project by the following command:

./gradlew bootRun

Explanation of the source code

The initialized project will contain an Application class, like this:

@SpringBootApplication
public class TodobackendApplication {

    public static void main(String[] args) {
        SpringApplication.run(TodobackendApplication.class, args);
    }

}

The SpringBootApplication decorator adds the additional functionality for the class to be run as a Spring Boot application.

Creating a ToDo model

To initiate a data entity (can be database table or collection), you will need to create a model class. In our case, we create a class named ToDo. The class has been initiated like this:

public class ToDo {
    @Id
    private String id;
    private String name;
    private String details;
    private boolean done;
}

This model class is independent of your database engine. Spring Boot will use this class to handle the ToDo objects in code layer. Also, note the Id decorator. Spring Boot will try to use the id field as the database table/collection id depending on your database engine.

Adding a Repository interface to communicate with the database

Spring Boot separates code layer from database layer, so that you can migrate from one database engine to another database engine with minimal change in your project. This is possible because communication with the database is handles with some special interfaces called Repository interfaces. We have a Repository interface for our ToDo model class named ToDoRepository. Which looks like this:

public interface ToDoRepository extends MongoRepository<ToDo, String> {
    //
}

Importantly, our ToDoRepository extends from the MongoRepository interface. MongoRepository interface has the support for communicating with the MongoDB, which will be useful for our case. Also note that, we are mapping our ToDo objects by String because the id field in ToDo class is a String field.

Preparing the RESTful API with Controller class

Spring Boot provides a very powerful decorator RestControl, which is very handy to prepare API endpoints. We initiate a controller class named ToDoController like this:

@RestController
public class ToDoController {
    private final ToDoRepository repository;

    public ToDoController(ToDoRepository repository) {
        this.repository = repository;
    }
}

Here, we set an instance of ToDoRepository as repository field to so that it is used for communicating with the database.

Preparing the API endpoints

We add methods in the ToDoController class to prepare the API endpoints. We will prepare API to perform the following things:

  • Get all objects by calling /todo in a GET request.
  • Get a single object by calling /todo/<id> in a GET request with the id of a ToDo instance.
  • Create a new ToDo instance by submitting an object in /todo in a POST request.
  • Update an existing ToDo instance by calling /todo/<id> in a PUT request.
  • Delete an existing ToDo instance by calling /todo/<id> in a DELETE request.

Spring Boot provides decorators such as GetMappingPostMappingPutMapping, and DeleteMapping for creating endpoints of each of these request methods.

Get all objects

We add the following method in ToDoController class which allows us to get all objects by calling /todo in a GET request.

    @GetMapping("/todo")
    List&lt;ToDo&gt; findAll() {
        return this.repository.findAll();
    }
Get a single object by id

We add the following method in ToDoController class which allows us to get a single object by calling /todo/<id> in a GET request with the id of a ToDo instance.

    @GetMapping("/todo/{id}")
    ToDo findById(@PathVariable String id) {
        return this.repository.findById(id).orElseThrow(() -&gt; new RuntimeException("Not found."));
    }

Note that, we are throwing a RuntimeException if a ToDo instance does not exist with the given id.

Creating a new instance

We add the following method in ToDoController class which allows us to create a new ToDo instance by submitting an object in /todo in a POST request.

    @PostMapping("/todo")
    ToDo save(@RequestBody ToDo toDo) {
        return this.repository.save(toDo);
    }
Updating an existing instance

We add the following method in ToDoController class which allows us to update an existing ToDo instance by calling /todo/<id> in a PUT request.

    @PutMapping("/todo/{id}")
    ToDo replaceById(@RequestBody ToDo newToDo, @PathVariable String id) {
        return this.repository.findById(id)
                .map(toDo -&gt; {
                    toDo.setName(newToDo.getName());
                    toDo.setDone(newToDo.isDone());
                    toDo.setDetails(newToDo.getDetails());
                    return repository.save(toDo);
                })
                .orElseGet(() -&gt; {
                    newToDo.setId(id);
                    return this.repository.save(newToDo);
                });
    }

Note that, if we find an existing ToDo instance with the provided id, we are updating it with the object submitted as the request body. But if there is no such ToDo instance is found, a new instance is created instead.

Delete an instance by id

We add the following method in ToDoController class which allows us to delete an existing ToDo instance by calling /todo/<id> in a DELETE request.

    @DeleteMapping("/todo/{id}")
    void deleteById(@PathVariable String id) {
        this.repository.deleteById(id);
    }

Enable CORS support

We are going to run this Spring Boot project as a standalone backend server and there will be frontend client applications which will be consuming the APIs. Therefore, it is necessary to enable CORS support so that, applications of other origins can access the API.

To enable CORS, we add the following code snippet in the TodobackendApplication class:

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                        .allowedMethods("GET", "POST", "PUT", "DELETE")
                        .allowedOrigins("*");
            }
        };
    }

Testing the Rest API endpoints

We are using Postman to test our API endpoints. Postman is a very nice tool to test and document your API and share across your team.

Please note that, we are running our server in localhost:8070. You can change the port from src/main/resources/application.properties file. We have set the port property by this line:

server.port=8070

First, check getting all ToDo entries:

We get the following response from this request:

  • Status: 200 OK
  • Response Body:
[]

It indicates that the API is working. It returned an empty array because we did not create any ToDo yet.

Let’s create a couple of ToDo entries:

  • Request URL: http://localhost:8070/todo
  • Request Method: POST
  • Request Header: {“Content-Type”: “application/json”}
  • Request Body:
{
	"name": "Task 1",
	"details": "I have to complete task 1.",
	"done": false
}

Response of the above request:

  • Status: 200 OK
  • Response Body:
{
    "id": "5f01822da0a607141dc94d30",
    "name": "Task 1",
    "details": "I have to complete task 1.",
    "done": false
}

The second one:

  • Request URL: http://localhost:8070/todo
  • Request Method: POST
  • Request Header: {“Content-Type”: “application/json”}
  • Request Body:
{
	"name": "Task 2",
	"details": "I have to complete task 2, too.",
	"done": false
}

Response of the above request:

  • Status: 200 OK
  • Response Body:
{
    "id": "5f018241a0a607141dc94d31",
    "name": "Task 2",
    "details": "I have to complete task 2, too.",
    "done": false
}

Now, let’s check again to read all ToDos:

We get the following response from this request:

  • Status: 200 OK
  • Response Body:
[
    {
        "id": "5f01822da0a607141dc94d30",
        "name": "Task 1",
        "details": "I have to complete task 1.",
        "done": false
    },
    {
        "id": "5f018241a0a607141dc94d31",
        "name": "Task 2",
        "details": "I have to complete task 2, too.",
        "done": false
    }
]

Checking retrieval of individual ToDo by id:

We get the following response from this request:

  • Status: 200 OK
  • Response Body:
{
    "id": "5f01822da0a607141dc94d30",
    "name": "Task 1",
    "details": "I have to complete task 1.",
    "done": false
}

Try updating a ToDo instance:

{
    "name": "Task 1",
    "details": "I have to complete task 1.",
    "done": true
}

We get the following response from this request:

  • Status: 200 OK
  • Response Body:
{
    "id": "5f01822da0a607141dc94d30",
    "name": "Task 1",
    "details": "I have to complete task 1.",
    "done": true
}

Checking if deletion by id is working:

We get the following response from this request:

  • Status: 200 OK

Finally, checking if things went as expected by reading all the ToDo entries again:

We get the following response from this request:

  • Status: 200 OK
  • Response Body:
[
    {
        "id": "5f01822da0a607141dc94d30",
        "name": "Task 1",
        "details": "I have to complete task 1.",
        "done": true
    }
]

Thus, we have confirmed that, all our API endpoints are working as expected.

You can find the complete source code of this project in this repository. Check out the part 2 here.

Leave a Reply

Your email address will not be published. Required fields are marked *