CodeGym /Java Blog /Java Developer /Overview of REST. Part 3: Building a RESTful service on S...

Overview of REST. Part 3: Building a RESTful service on Spring Boot

Published in the Java Developer group
This is the final part of our overview of REST. In the previous parts, we covered: Overview of REST. Part 3: Building a RESTful service on Spring Boot - 1

Creating a project

In this section, we will create a small RESTful application using Spring Boot. Our application will implement CRUD (Create, Read, Update, Delete) operations on the customers from the example in the previous part of the overview. To start, we'll create a new Spring Boot application through the menu: File -> New -> Project... In the window that opens, select Spring Initializr and specify the Project SDK: Overview of REST. Part 3: Building a RESTful service on Spring Boot - 2Click the "Next" button. In the next window, specify "Maven Project" as the project type, specify the "Group" and "Artifact": Overview of REST. Part 3: Building a RESTful service on Spring Boot - 3Click the "Next" button. In the next window, we need to select the Spring Framework components necessary for the project. Spring Web will be sufficient for us: Overview of REST. Part 3: Building a RESTful service on Spring Boot - 4Click the "Next" button. Now all that remains is to indicate the name of the project and its location in the file system: Overview of REST. Part 3: Building a RESTful service on Spring Boot - 5Click the "Finish" button. The project is created, and now we can see its structure: Overview of REST. Part 3: Building a RESTful service on Spring Boot - 6IDEA generated a Maven deployment descriptor (pom.xml) and the application's main class (RestExampleApplication) for us. Here's what they look like:

pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <parent>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-parent</artifactId>
       <version>2.2.2.RELEASE</version>
       <relativePath/> <!-- lookup parent from repository -->
   </parent>
   <groupId>com.codegym.lessons/groupId>
   <artifactId>rest_example</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>rest_example</name>
   <description>REST example project</description>

   <properties>
       <java.version>1.8</java.version>
   </properties>

   <dependencies>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
       </dependency>

       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-test</artifactId>
           <scope>test</scope>
           <exclusions>
               <exclusion>
                   <groupId>org.junit.vintage</groupId>
                   <artifactId>junit-vintage-engine</artifactId>
               </exclusion>
           </exclusions>
       </dependency>
   </dependencies>

   <build>
       <plugins>
           <plugin>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-maven-plugin</artifactId>
           </plugin>
       </plugins>
   </build>

</project>
RestExampleApplication:

@SpringBootApplication
public class RestExampleApplication {

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

}

Creating REST functionality

Our application is a customer management system. So, the first thing we need to do is create a customer entity. It will be a POJO (plain old Java object) class. Create a model package inside the com.codegym.lessons.rest_example package. Inside the model package, create the Customer:

public class Customer {

   private Integer id;
   private String name;
   private String email;
   private String phone;

   public Integer getId() {
       return id;
   }

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

   public String getName() {
       return name;
   }

   public void setName(String name) {
       this.name = name;
   }

   public String getEmail() {
       return email;
   }

   public void setEmail(String email) {
       this.email = email;
   }

   public String getPhone() {
       return phone;
   }

   public void setPhone(String phone) {
       this.phone = phone;
   }
}
The service will implement CRUD operations on customers. The next step is to create a service that will implement these operations. In the com.codegym.lessons.rest_example package, create a service package. And inside that, create a CustomerService interface. Here is the interface code with comments:

public interface CustomerService {

   /**
    * Creates a new customer
    * @param customer - Customer to be created
    */
   void create(Customer customer);

   /**
    * Returns a list of all existing customers
    * @return List of customers
    */
   List<Customer> readAll();

   /**
    * Returns a customer based on its ID
    * @param id - Customer ID
    * @return - Customer object with the given ID
    */
   Customer read(int id);

   /**
    * Updates the customer with the given ID,
    * according to the passed customer
    * @param customer - Customer to use to update the data
    * @param id - ID of the customer you want to update
    * @return - true if the data has been updated, otherwise false
    */
   boolean update(Customer customer, int id);

   /**
    * Deletes the customer with the given ID
    * @param id - ID of the customer to be deleted
    * @return - true if the customer was deleted, otherwise false
    */
   boolean delete(int id);
}
Next, we need to implement this interface. Now a Map<Integer, Customer> will store our customers. The map's keys will be the customer IDs, and the values will be the customers themselves. This is done so as to not overload this example with the specifics of working with a real database. However, in the future we will be able to write another implementation of the interface, which will make it possible to connect to a real database. In the service package, create an implementation of the CustomerService interface:

@Service
public class CustomerServiceImpl implements CustomerService {

   // Customer repository
   private static final Map<Integer, Customer> CUSTOMER_REPOSITORY_MAP = new HashMap<>();
  
   // Variable for generating a customer ID
   private static final AtomicInteger CUSTOMER_ID_HOLDER = new AtomicInteger();

   @Override
   public void create(Customer customer) {
       final int customerId = CUSTOMER_ID_HOLDER.incrementAndGet();
       customer.setId(customerId);
       CUSTOMER_REPOSITORY_MAP.put(customerId, customer);
   }

   @Override
   public List<Customer> readAll() {
       return new ArrayList<>(CUSTOMER_REPOSITORY_MAP.values());
   }

   @Override
   public Customer read(int id) {
       return CUSTOMER_REPOSITORY_MAP.get(id);
   }

   @Override
   public boolean update(Customer customer, int id) {
       if (CUSTOMER_REPOSITORY_MAP.containsKey(id)) {
           customer.setId(id);
           CUSTOMER_REPOSITORY_MAP.put(id, customer);
           return true;
       }

       return false;
   }

   @Override
   public boolean delete(int id) {
       return CUSTOMER_REPOSITORY_MAP.remove(id) != null;
   }
}
The @Service annotation tells spring that this class is a service. This is a special type of class that implements some business application logic. Subsequently, thanks to this annotation, Spring will use dependency injection to provide us with an instance of this class in all the places where it is needed. Now it's time to create a controller. This is a special class where we will implement the logic for processing client requests sent to endpoints (URIs). To make all this clearer, we will create this class incrementally. First, create the class itself and add a dependency on CustomerService:

@RestController
public class CustomerController {

   private final CustomerService customerService;

   @Autowired
   public CustomerController(CustomerService customerService) {
       this.customerService = customerService;
   }
}
Let's explain the annotations: @RestController tells Spring that this class is a REST controller. In other words, this class implements the logic for processing client requests. @Autowired tells Spring that a dependency needs to be added here. We pass the CustomerService interface to the constructor. Earlier, we marked the implementation of this service with the @Service annotation, and now Spring will be able to pass an instance of this implementation to the controller's constructor. Next, we will implement each controller method for handling CRUD operations. Let's start with the create operation. To do this, we write a create method:

@PostMapping(value = "/customers")
public ResponseEntity<?> create(@RequestBody Customer customer) {
   customerService.create(customer);
   return new ResponseEntity<>(HttpStatus.CREATED);
}
Let's analyze this method: @PostMapping(value = "/customers") mean that this method processes POST requests sent to the address "/customers". The method returns a ResponseEntity<?>. A ResponseEntity is a special class for returning responses. Later, we will use it to return an HTTP status code to the client. The method has a @RequestBody Customer customer parameter. The value of this parameter comes from the request body. The @RequestBody annotation indicates this. Inside the body of the method, we call the create() method on the previously created service and pass it the customer controller received in the parameters. Then we return the "201 Created" status by creating a new ResponseEntity object and passing the corresponding HttpStatus enum field to it. Next, we'll implement the read operation: First, we'll implement the operation to get a list of all available customers:

@GetMapping(value = "/customers")
public ResponseEntity<List<Customer>> read() {
   final List<Customer> customers = customerService.readAll();

   return customers != null &&  !customers.isEmpty()
           ? new ResponseEntity<>(customers, HttpStatus.OK)
           : new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
Let's dive in: @GetMapping(value = "/customers") — everything here is similar to the @PostMapping annotation, but now we are processing GET requests. This time we return a ResponseEntity<List<Customer>>, and in addition to an HTTP status, we will also return a response body, which will be the list of customers. In Spring's REST controllers, everything is POJO objects and collections of POJO objects, which are returned as response bodies and automatically serialized into JSON, unless otherwise specified. This suits us perfectly. Inside the method, we use our service to get a list of all customers. Next, if the list is not null and not empty, then we use the ResponseEntity class to return the list of customers and the "200 OK" HTTP status code. Otherwise, we simply return the "404 Not Found" HTTP status code. Now we will implement the ability to get a customer using its ID:

@GetMapping(value = "/customers/{id}")
public ResponseEntity<Customer> read(@PathVariable(name = "id") int id) {
   final Customer customer = customerService.read(id);

   return customer != null
           ? new ResponseEntity<>(customer, HttpStatus.OK)
           : new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
One new thing here is the path variable. The variable is defined in the URI: value = "/customers/{id}". We indicate it in curly braces. And we receive it as an int method parameter using the @PathVariable(name = "id") annotation. This method will accept requests sent to URIs in the form /customers/{id}, where {id} represents any numeric value. This value is subsequently passed via the int id variable to the method parameter. In the body, we get the Customer object using our service and the received id. And then, by analogy with the list, we return either the "200 OK" status and the Customer object itself, or simply the "404 Not Found" status if the system has no customer with that id. We still need to implement two operations: update and delete. Here is the code for these methods:

@PutMapping(value = "/customers/{id}")
public ResponseEntity<?> update(@PathVariable(name = "id") int id, @RequestBody Customer customer) {
   final boolean updated = customerService.update(customer, id);

   return updated
           ? new ResponseEntity<>(HttpStatus.OK)
           : new ResponseEntity<>(HttpStatus.NOT_MODIFIED);
}

@DeleteMapping(value = "/customers/{id}")
public ResponseEntity<?> delete(@PathVariable(name = "id") int id) {
   final boolean deleted = customerService.delete(id);

   return deleted
           ? new ResponseEntity<>(HttpStatus.OK)
           : new ResponseEntity<>(HttpStatus.NOT_MODIFIED);
}
There's nothing essentially new in these methods, so we'll skip the detailed description. The only thing worth mentioning is that the update() method handles PUT requests (@PutMapping annotation), and the delete() method handles DELETE requests (DeleteMapping annotation). Here is the full code for the controller:

@RestController
public class CustomerController {

   private final CustomerService customerService;

   @Autowired
   public CustomerController(CustomerService customerService) {
       this.customerService = customerService;
   }

   @PostMapping(value = "/customers")
   public ResponseEntity<?> create(@RequestBody Customer customer) {
       customerService.create(customer);
       return new ResponseEntity<>(HttpStatus.CREATED);
   }

   @GetMapping(value = "/customers")
   public ResponseEntity<List<Customer>> read() {
       final List<Customer> customers = customerService.readAll();

       return customers != null &&  !customers.isEmpty()
               ? new ResponseEntity<>(customers, HttpStatus.OK)
               : new ResponseEntity<>(HttpStatus.NOT_FOUND);
   }

   @GetMapping(value = "/customers/{id}")
   public ResponseEntity<Customer> read(@PathVariable(name = "id") int id) {
       final Customer customer = customerService.read(id);

       return customer != null
               ? new ResponseEntity<>(customer, HttpStatus.OK)
               : new ResponseEntity<>(HttpStatus.NOT_FOUND);
   }

   @PutMapping(value = "/customers/{id}")
   public ResponseEntity<?> update(@PathVariable(name = "id") int id, @RequestBody Customer customer) {
       final boolean updated = customerService.update(customer, id);

       return updated
               ? new ResponseEntity<>(HttpStatus.OK)
               : new ResponseEntity<>(HttpStatus.NOT_MODIFIED);
   }

   @DeleteMapping(value = "/customers/{id}")
   public ResponseEntity<?> delete(@PathVariable(name = "id") int id) {
       final boolean deleted = customerService.delete(id);

       return deleted
               ? new ResponseEntity<>(HttpStatus.OK)
               : new ResponseEntity<>(HttpStatus.NOT_MODIFIED);
   }
}
As a result, the structure of our project is as follows: Overview of REST. Part 3: Building a RESTful service on Spring Boot - 7

Launching and testing

To start our application, just run the main() method in the RestExampleApplication class. But to test RESTful web services, we need to download additional software. The fact is that GET requests are quite simple to send from an ordinary browser, but an ordinary browser can't send POST, PUT and DELETE requests. Don't worry: you can use a program called Postman to send any HTTP requests. You can download it here. After downloading and installing Postman, we begin testing our application. To do this, open the program and create a new request: Overview of REST. Part 3: Building a RESTful service on Spring Boot - 9Click the "New" button in the upper left corner. Next, select "Request": Overview of REST. Part 3: Building a RESTful service on Spring Boot - 10Next, give it a name and save it. Now let's try to send a POST request to the server and create the first customer: Overview of REST. Part 3: Building a RESTful service on Spring Boot - 11We create several customers in this way. Then we change the request type to GET and send the request to the server: Overview of REST. Part 3: Building a RESTful service on Spring Boot - 12

Summary

Congratulations! We've sufficiently covered REST. There was a large volume of material, but hopefully it was useful for you:
  1. We learned what REST is.

  2. We learned about how REST came into existence.

  3. We talked about the limitations of and principles behind this architectural style:

    • client-server architecture
    • stateless
    • caching
    • uniform interface
    • layers
    • code on demand (optional)
  4. We explored the benefits provided by REST

  5. We examined in detail how the server and client interact with each other via the HTTP protocol.

  6. We took a closer look at requests and responses. We dissected their constituent parts.

  7. Finally, we got some practical experience by writing our own small RESTful application using Spring Boot. And we even learned how to test it using Postman.

Phew. That was a lot, but there is still something for you to do as homework.

Homework

Try the following:
  1. Following the description above, create your own Spring Boot project and implement the same logic as in the lesson. Repeat everything exactly.
  2. Launch the application.
  3. Download and configure Postman (or any other tool for sending requests, for example, curl).
  4. Test POST and GET requests in the same way described in the lesson.
  5. Test PUT and DELETE requests yourself.
Comments (1)
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION
Vesa Level 41, Kaliningrad, Russia
19 October 2020
These are great articles. Everything worked out!