avatarMichelle Wiginton

Summary

The provided content is a comprehensive tutorial on building a CRUD (Create, Read, Update, Delete) REST API for a ToDo list application using the Java Spring Boot framework.

Abstract

The tutorial guides developers through the process of setting up a Spring Boot project, designing a data model for a ToDo list, and implementing a repository for database interactions. It covers the creation of a Todo entity with fields for id, title, and completed status, along with constructors and getter/setter methods. The repository is established using Spring Data JPA, which extends JpaRepository to provide CRUD methods without explicit implementation. Additionally, the tutorial demonstrates the construction of a TodoController class to handle HTTP requests and integrate the ApiResponse class for standardized API responses. The article also includes instructions on running and testing the application using tools like SpringToolSuite and Postman, ensuring the API endpoints function correctly. The conclusion summarizes the skills acquired, such as initializing a Spring Boot project, designing a data model, creating a repository, building a controller, and testing API endpoints. The tutorial aims to enhance the developer's understanding of Spring Boot and API development, providing a solid foundation for managing and manipulating data in real-world applications.

Opinions

  • The author emphasizes the importance of having a solid grasp of Java programming before starting the tutorial.
  • The use of Spring Initializr is recommended for setting up the foundation of a Spring Boot project efficiently.
  • Maven is chosen as the build tool in the example, but Gradle is also presented as a viable option.
  • The H2 Database is favored for its convenience in development and testing environments.
  • The ApiResponse class is introduced to standardize API responses, which is seen as a best practice for API development.
  • Error handling is demonstrated using custom exceptions, highlighting the importance of a robust error management strategy.
  • The tutorial encourages the use of Postman for API testing, suggesting its effectiveness in verifying endpoint functionality.
  • The author invites feedback and improvement suggestions on the tutorial, showing a commitment to continuous learning and community engagement.
  • A call to action is made for readers to subscribe to Medium using the author's referral link, indicating a desire to support the platform and its content creators.

Building a ToDo List CRUD API with the Java Spring Boot Framework

Introduction

Creating a fully functional CRUD (Create, Read, Update, Delete) REST API is a crucial skill for software engineers. In this tutorial, we’ll walk through the process of creating a ToDo list CRUD API using the Java Spring Boot framework. By the end of this tutorial, you will have a solid grasp of constructing APIs with Spring Boot, enabling you to effectively manage and manipulate data. The completed source code for this example can be found in the following Github Repository.

Prerequisites

Before starting this tutorial, make sure you have the following tools installed on your system. This article also assumes you have a basic understanding of Java programming.

  • Java Development Kit (JDK)
  • An Integrated Development Environment (IDE) of your choosing like IntelliJ IDEA or Eclipse
  • Postman or a similar API testing tool

Step 1: Setting Up Our Project

  1. Visit the Spring Initializr at https://start.spring.io/. This is the web based setup wizard that will help us set up the foundation of our Spring Boot project.
  2. Choose Maven or Gradle as the build tool and Java as the programming Language. In our example we will select Maven as our build tool.
Selecting Maven as our build tool and Java as the language

3. Add dependencies: Spring Web, Spring Data JPA, H2 Database.

  • Spring Web: Enables us to build and manage our API’s web layer, handling incoming requests and sending responses
  • Spring Data JPA: Gives us simplified access to a relational database, making it easier to manage, query, and manipulate our data.
  • H2 Database: An in-memory database that facilitates testing and development, allowing us to interact with data without setting up an external database initially.
Selecting the Spring Web, Spring Data JPA, and H2 Database dependencies for our project

4. Fill in your Project Metadata:

  • Group: This represents the base package for your project’s classes. It’s usually in reverse domain format (e.g., com.mycompany). In this example our group will be the default com.example
  • Artifact: This is the name of your application or project. In this example our artifact will be todolist
  • Name: Provide a descriptive name for your project. The name will also be todolist
  • Description: Briefly describe your project’s purpose. In this example our description will be To-Do List API’s
  • Package Name: This is automatically generated based on your Group and Artifact values. In this example our package name will be com.example.todolist.
  • Packaging: Choose Jar or War based on your deployment needs. In this example we chose Jar.
  • Java: Select the version of Java you want to use (e.g., 11, 16). In this example we are working with Java version 17.
Configuration of our Project Metadata

5. Click the Generate button to create your project.

6. Unzip the contents of the generated project and import the project into the IDE of your choosing. In our example we are using the SpringToolSuite IDE powered by Eclipse. In order to import the project into SpringToolSuite you can do the following

  • File -> Import Projects From File System…
  • In the Import Source field, include the directory of your unzipped project then click Finish

Step 2: Designing the Data Model

Create the ToDo Entity and Import Dependencies

In your preferred IDE create a Java class called Todo in your default package. This class will serve as our entity representing a ToDo item.

package com.example.todolist;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Todo {}

Define Fields

Inside the Todo class, define fields that represent the properties of a ToDo item. For example, add fields like id, title, and completed.

@Entity
public class Todo {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;
    private boolean completed;
}

Notice that the id field is annotated with @Id to mark it as the primary key.

The @GeneratedValue(strategy = GenerationType.IDENTITY) indicates that the id will be automatically generated when a new ToDo item is created.

Create the Constructors

Next we will create our constructors for our Todo entity. We’ll have two constructors: the default constructor and the parameterized constructor.

The default constructor doesn’t take any arguments and can be useful when creating instances without specific initial values. It’s created automatically if you don’t define any constructors.

The parameterized constructor takes title and completed as arguments. It allows us to provide these values during object creation. The this keyword is used to refer to the instance being created and set its attributes accordingly.

    public Todo() {
    }

    public Todo(String title, boolean completed) {
        this.title = title;
        this.completed = completed;
    }

Creating the Getters and Setters

After defining our constructors, we will next create our getter and setter methods. Getters and setter methods allow controlled access to the attributes of a class. In our Todo class, we'll implement these methods to interact with the id, title, and completed fields.

A getter method retrieves the value of an attribute, and a setter method assigns a value to an attribute.

By manually creating these methods, we encapsulate access to the attributes, ensuring that they are accessed and modified consistently. Below you can see the completed Todo entity with its attributes, constructors, getter, and setter methods.

package com.example.todolist;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Todo {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String title;
    private boolean completed;
    
    // Constructors
    public Todo() {
    }

    public Todo(String title, boolean completed) {
        this.title = title;
        this.completed = completed;
    }
    
    // Getters and Setters
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public boolean isCompleted() {
        return completed;
    }

    public void setCompleted(boolean completed) {
        this.completed = completed;
    }
}

In this step, we’ve laid the groundwork for our ToDo list CRUD API with the Todo entity. We created the Todo class as a JPA entity, featuring attributes id, title, and completed. We designed constructors to initialize instances with or without values, and implemented getters and setters to ensure controlled attribute access. This entity is now ready for database persistence. In the next step, we will create our Repository class

Step 3: Creating the Repository

In this step, we’ll establish the repository that acts as a bridge between our application and the database. The repository provides methods to perform CRUD (Create, Read, Update, Delete) operations on our Todo entities.

Create the Repository Interface

Inside your project, in the same package as your Todo entity class, create a new Java interface named TodoRepository. This interface will extend JpaRepository<Todo, Long>, where Todo is the entity class and Long is the type of the entity's primary key.

package com.example.todolist;

import org.springframework.data.jpa.repository.JpaRepository;

public interface TodoRepository extends JpaRepository<Todo, Long> {
}

Examining our Interface

The JpaRepository is an interface provided by Spring Data JPA. It offers a collection of predefined methods for database operations which you will later see called in our Controller class that we will implement. By extending JpaRepository, our TodoRepository gains methods like save, findById, findAll, delete, and more, without requiring explicit implementation.

By creating the TodoRepository interface, you've laid the foundation for accessing and manipulating ToDo data within the database (in this case our in memory H2 Database). In the upcoming steps, we'll build upon this by creating the controller and implementing CRUD operations that utilize this repository to manage ToDo items.

Step 4: Creating the ApiResponse Class

Before proceeding to build the controller, let’s introduce the ApiResponse class. This class will standardize our API responses, providing consistent feedback to clients whether an operation succeeds or encounters an error.

Create the ApiResponse Class

Within your project, in the same package as your Todo entity and TodoRepository, create a new Java class named ApiResponse. This class will encapsulate the structure of our API responses.

package com.example.todolist;

public class ApiResponse {
    private boolean success;
    private String message;
    private Object data;

    public ApiResponse(boolean success, String message, Object data) {
        this.success = success;
        this.message = message;
        this.data = data;
    }

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
}

Examining the ApiResponse Class

The ApiResponse class comprises the following attributes: success, message, and data.

  • success indicates whether an operation was successful or not.
  • message provides an informative message about the outcome.
  • data holds the payload, such as retrieved entities or other relevant data.

Benefits of the ApiResponse Class

The ApiResponse class brings valuable benefits to our API structure:

  • It establishes a standardized response format, ensuring clarity and consistency.
  • Users receive clear and informative feedback, both for successful operations and errors.
  • Effective error handling and data extraction are enabled on the client side.

By incorporating the ApiResponse class, we enhance the overall user experience and simplify the management of API responses throughout the application. This will be particularly valuable as we proceed to build and handle CRUD operations in our controller.

Step 5: Building the Controller

In this step, we will create the TodoController class, which serves as the entry point for handling API requests related to ToDo list items. Through this controller, we'll implement endpoints for various HTTP methods such as GET, POST, PUT, and DELETE. We'll also explore how we manage errors and provide standardized responses using the ApiResponse class.

package com.example.todolist;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import com.example.todolist.exception.ResourceNotFoundException;

@RestController
@RequestMapping("/api/todos")
public class TodoController {
    private final TodoRepository todoRepository;

    @Autowired
    public TodoController(TodoRepository todoRepository) {
        this.todoRepository = todoRepository;
    }

    @GetMapping
    public ResponseEntity<ApiResponse> getAllTodos() {
        try {
            List<Todo> todos = todoRepository.findAll();
            return ResponseEntity.ok(new ApiResponse(true, "Todos retrieved successfully", todos));
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                                 .body(new ApiResponse(false, "Error retrieving todos", null));
        }
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<ApiResponse> getTodoById(@PathVariable Long id) {
        try {
            Todo todo = todoRepository.findById(id)
                    .orElseThrow(() -> new ResourceNotFoundException("Todo not found with id: " + id));

            return ResponseEntity.ok(new ApiResponse(true, "Todo retrieved successfully", todo));
        } catch (ResourceNotFoundException e) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND)
                                 .body(new ApiResponse(false, "Error retrieving todo with id: " + id, null));
        }
    }

    @PostMapping
    public ResponseEntity<ApiResponse> createTodo(@RequestBody Todo todo) {
        try {
            Todo createdTodo = todoRepository.save(todo);
            return ResponseEntity.status(HttpStatus.CREATED)
                                 .body(new ApiResponse(true, "Todo created successfully", createdTodo));
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                                 .body(new ApiResponse(false, "Error creating todo", null));
        }
    }

    @PutMapping("/{id}")
    public ResponseEntity<ApiResponse> updateTodo(@PathVariable Long id, @RequestBody Todo todoDetails) {
        try {
            Todo todo = todoRepository.findById(id)
                    .orElseThrow(() -> new ResourceNotFoundException("Todo not found with id: " + id));

            todo.setTitle(todoDetails.getTitle());
            todo.setCompleted(todoDetails.isCompleted());

            Todo updatedTodo = todoRepository.save(todo);
            return ResponseEntity.ok(new ApiResponse(true, "Todo updated successfully", updatedTodo));
        } catch (ResourceNotFoundException e) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND)
                                 .body(new ApiResponse(false, "Error updating todo with id: " + id, null));
        }
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<ApiResponse> deleteTodo(@PathVariable Long id) {
        try {
            Todo todo = todoRepository.findById(id)
                    .orElseThrow(() -> new ResourceNotFoundException("Todo not found with id: " + id));

            todoRepository.delete(todo);
            return ResponseEntity.ok(new ApiResponse(true, "Todo deleted successfully", null));
        } catch (ResourceNotFoundException e) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND)
                                 .body(new ApiResponse(false, "Error deleting todo with id: " + id, null));
        }
    }
}

In this completed TodoController class, we've built an essential layer for managing our ToDo list items via API endpoints. We've addressed common HTTP methods, including GET, POST, PUT, and DELETE, and integrated the ApiResponse class to standardize our responses. Additionally, we've demonstrated error handling using the ResourceNotFoundException for instances where specific ToDo items aren't found. Our ResourceNotFoundException class is simply just a class that extends the RunTimeException class.

With the TodoController in place, we've effectively established a controlled and organized means of interacting with our API, enabling users to perform CRUD operations on the ToDo list items.

Section 5: Running and Testing your Application

Running the Application

Now that we have finished implementing our endpoints, it’s time to run and test our API’s. Depending on whether you run your project from your IDE or terminal is up to you. In the case of this example we will run our project from the IDE.

In our SpringToolSuite IDE, right click on your project under the local server and select (Re)start then verify that your project has successfully started in the console.

Run our project and verify that it has successfully started

Testing our Application

Now that we have started our API server, the last thing we want to do is test each of our endpoints and verify that they are functioning. In this example we will be using Postman to test our endpoints.

Testing GET Endpoints

To test GET endpoints like fetching all todos or a specific todo by ID:

  • Select the request type as “GET.”
  • Enter the URL for the desired endpoint (e.g., http://localhost:8080/api/todos for all todos and http://localhost:8080/api/1 (where 1 is an id) to get a todo by ID.
  • Click the “Send” button to receive the response Below is a sample response for retrieving all todos.
{
    "success": true,
    "message": "Todos retrieved successfully",
    "data": [
        {
            "id": 1,
            "title": "Get gas",
            "completed": false
        },
        {
            "id": 2,
            "title": "Get Groceries",
            "completed": false
        }
    ]
}

Testing POST, PUT, and DELETE Endpoints

For POST, PUT, and DELETE requests, follow similar steps:

  • Select the respective request type.
  • Set the URL for the desired endpoint (e.g., http://localhost:8080/api/todos for creating a todo).
  • In the “Body” section, provide the necessary data in JSON format (for POST and PUT requests). Sample Request Body for creating a todo:
{
    "title": "Get Groceries",
    "completed": false
}
  • Click the “Send” button to observe the response. Sample Response body after creating todo:
{
    "success": true,
    "message": "Todo created successfully",
    "data": {
        "id": 2,
        "title": "Get Groceries",
        "completed": false
    }
}

Conclusion

This concludes our tutorial on developing and testing a CRUD REST API Project using the Java Spring Boot Framework.

In summary, you’ve learned to:

  • Initialize a Spring Boot project with essential dependencies using the Spring Initializr.
  • Design a data model by creating the Todo entity with attributes and methods.
  • Create a repository for seamless interaction with the database.
  • Build a controller to handle API requests and perform CRUD operations.
  • Test your API endpoints using Postman to ensure functionality and reliability.

Feel free to share any feedback on how this example can be improved and I hope this article has been helpful. Original documentation: https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/

Source Code: https://github.com/mwiginton/springboot-rest-crud-example

If you enjoyed reading this article, please consider signing up for Medium if you haven’t already done so using my referral link. This subscription ensures unlimited access to my articles plus thousands of other talented writers across many disciplines.

Software Engineering
Programming
Technology
Java
Spring Boot
Recommended from ReadMedium