Learn, Build, Deploy

Building a RESTful CRUD API using Spring Boot – Part 5

Table of Contents

Creating the Controller Layer

Finally, the time has come where we can connect the last few dots, and get the RMS running, accepting requests from a client, and seeing the everything come to life!
As the Service layer has been completed, we need to have a way to accept requests from the outside world. Depending on the request, we can trigger different CRUD operations within our RMS.

Before we get started, we should probably go through some basics about how our the RMS can accept requests in the first place (do skip to the next part if you already know this stuff! πŸ˜‰).

How can services interact with each other?

A very common way that requests are sent from one place to another is using REST over HTTP.

OperationHTTP VerbHTTP Use CaseOur Use Case
CreatePOSTMake a resourceCreating a reservation
ReadGETGet a resourceRetrieving a reserivation
UpdatePUTUpdate a resourceUpdtaing a reservation
DeleteDELETERemove a resourceDeleting a reservation
  • HTTP – Defines a set of methods to indicate the desired action to be performed for a given resource. We can make use of these to fulfil the operations that need to be completed.
  • RESTRepresentational State Transfer. This provides a standard between services, which makes it easier for inter-service communication. Back in the day, when you requested a webpage, sometimes it would look like
http://www.exmaple.com/asdkafuliofiualdhd
  • To us mere mortals, unfortunately, this means absolutely nothing
  • REST changes this to help us give our endpoints a little more meaning e.g.
http://www.exmaple.com/api/v1/reservation
  • This makes so much more sense, as it is clearly readable, and it’s easy to understand. We are interacting with v1 of this API and interacting with the reservation endpoint.
  • This is what REST attempts to solve. So in Part 1, when I said we are building a RESTful application, now you know what it means πŸ˜„.
  • REST over HTTP – By combining the two, REST over HTTP means that we can use the various HTTP verbs in combination with this nice readable URI style, to make it easy for others to interact with our API.

In our ReservationRepository layer, we made use of the CrudRepository interface, which has a collection of different CRUD methods that we made use of.

What is the Controller Layer?

This layer contains various entry points to our system. It will accept these various HTTP requests, and then delegate the request to the appropriate service to handle the request for us.

How does the controller layer work?

  • When a request enters the application, it will be sent to the HTTP method to handle that HTTP request. You can think of a Controller as a class that controls where your request is going to go.
  • The request data can come in a variety of different forms – JSON, XML, path variables or query parameters.
  • Once the request goes to the appropriate Controller method, the Jackson library (part of the spring-boot-starter-web dependency) will convert the request data for us into a Java object.
  • The controller method can then pass the object to the appropriate service method to handle the request for the client.
  • Once the Service layer gives back a response, the Controller will serialise the response object back into JSON, or XML, and return this back to the client that made the request along with an appropriate HTTP status code.

Still with me? πŸ˜‰ I know it’s a lot of information, but this is key information that you need to have! I will make a post to explain this in more depth, as this stuff is important to know if you want to be better than the average Java Spring developer πŸ˜‰.

Implementing the ReservationController

  • Create a new package called controller within com.rms.reservationservice.
  • Now create a class called ReservationController. This will contain all the Controller methods that will let us trigger the Create, Read, Update and Deleting flows in the ReservationService, based on the HTTP request type and the endpoint provided.
package com.rms.reservationservice.controller;

import com.rms.reservationservice.service.ReservationService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/v1/reservation")
public class ReservationController {
    public final ReservationService reservationService;

    public ReservationController(ReservationService reservationService) {
        this.reservationService = reservationService;
    }
}
  • @RestController – Indicating to Spring that this is a Spring Component, just like @Service and @Repository. This will allow other Spring beans to be injected into this class.
  • @RestController is also a convenience annotation that combines @Controller and @ResponseBody – which eliminates the need to annotate every request handling method of the controller class with the @ResponseBody annotation.
  • @ResponseBody – This annotation tells a Controller that the object returned should be automatically serialised back into JSON and sent back to the client.
  • @RequestMapping – Tells Spring that to interact with this Controller class, you must provide the following path: /api/v1/reservation as a path prefix. All requests will now look something like
http://www.example.com/api/v1/reservation

So why did we build the ReservationService? πŸ€”

To complete our various reservation flows, we need some entry points to our application that can interact with the ReservationService. As such, we can inject the ReservationService dependency into the controller directly, so that we can start to make use of the methods that the interface has to offer. OH YEAH, IT’S ALL COMIN’ TOGETHER NOW! Let’s break each of our flows down one by one:

Creating a New Reservation

When we think of “creating a new reservation”, this can also be thought of as “creating a new resource”. We are taking a reservation request, and when we save it to the database, we are creating a new row in our database table. Since we know it is a “Creation” command, this is where we would use the “POST” HTTP verb:

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.http.HttpStatus;

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Reservation save(@RequestBody Reservation reservation) {
    return reservationService.saveReservation(reservation);
}

…Let’s understand all these new Controller annotations…

  • @PostMapping – Indicates to Spring that the save() method will accept POST requests, which we can use to create a reservation.
  • @RequestBody – Jackson will take the incoming request and will convert it into a Reservation object, which is then passed into the method as a parameter.
  • save() will take the reservation object that has entered the controller, and delegate this to the reservationService. Once saved successfully, then return back a the new reservation object. This will then be converted back into JSON, and delivered back to the client. (Remember, thats the role of @ResponseBody)
  • @ResponseStatus – By default, once the request is completed successfully, the client will receive a HTTP status of 200 OK, along with the response body. We can decide what the HTTP status code will be when the method is completed successfully and then response is given back to the client. In our case, since we are creating a new resource, we can return back a 201 CREATED response instead!

If you did get stuck at any point, please checkout branch part-5-implementing-POST-save and compare your code against mine.

We have just created our first full flow of execution! πŸŽ‰πŸŽ‰πŸŽ‰πŸŽ‰

BUT HOW CAN WE TEST THIS?!

Let’s start up our application and send a POST request with some reservation data so that we can see a reservation being created. For the demo, we shall use the POSTMAN HTTP client:
(Make sure that you create your request request body in the same way I have before hitting the send button):
Create request and response

The response body given back to us:

  • The id field?? – The unique ID that lets us identify the reservation
  • The duration field – Indicating that since only 2 guests are coming, they can only stay a maximum of 1 hour.
  • Status 201 Created – Rather than the default 200 response, we returned back a 201 as a this indicates a new resource (a reservation) has been created.

Reading an existing Reservation

Imagine a customer has already made a reservation and wants to see their reservation that they had made. We want to GET this reservation so that the client can see the reservation. We need some unique way of identifying that specific customer booking.

Can you remember which HTTP verb you would use for retrieve a reservation?

That’s right! it’s a GET request! πŸ™Œ Lets get this put into code:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.http.HttpStatus;

@GetMapping("/{reservationId}")
@ResponseStatus(HttpStatus.OK)
public Reservation get(@PathVariable("reservationId") String reservationId) {
        return reservationService.getReservationById(reservationId);
}
  • @GetMapping – Tells Spring that get() will accept HTTP GET requests, which we can use to get an existing reservation using the reservationId. Note, we are passing in a path /{reservationId} so the URL would look like
http://localhost:8080/api/v1/reservation/JEBS-12345
  • @PathVariable – Enables the reservationId to be passed in as part of the URI. The path variable name reservationId must be the same as the name provided in the @GetMapping annotation.
  • Jackson will take this path variable, so that we can pass it to the reservationService’s getReservationById() method.
    Once we get a response, we return back a Reservation object back to the client, with a HTTP status of 200 if successful.

If you did get stuck at any point, please checkout branch part-5-implementing-GET-get and compare your code against mine.

TIME TO TEST!

  • When we start the application, we will not have any data to work with, so let’s start by saving a new reservation. Follow the steps on saving a new reservation to do this.
  • Once saved, we will have the reservation id which we can use to now fetch the existing reservation from the database.
  • Pass this into the URL as you can see below and assuming the reservationId exists, you will get back the reservation object that was persisted to the database.
    Read request and response

Updating an existing Reservation

Imagine a customer has already made a reservation, and wants to update the number of people from 2 to 9. We want to update this reservation with all the new fields so the RMS has the most relevant information about the customer’s reservation.
For this, we should use the PUT HTTP verb.

import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestBody;

@PutMapping("/{reservationId}")
@ResponseStatus(HttpStatus.OK)
public Reservation update(@PathVariable("reservationId") String reservationId, @RequestBody Reservation reservation) {
    return reservationService.updateReservation(reservationId, reservation);
}
  • @PutMapping – tells Spring that this method will accept PUT requests, which we can use to update an existing reservation using the reservationId and the @RequestBody provided.

If you did get stuck at any point, please checkout branch part-5-implementing-PUT-update and compare your code against mine.

TIME TO TEST!

Again, let’s now run our service, save something to it, and then update the reservation with new data:

Update request and response

Awesome! Now we have the ability to save new reservations, get them back from the database, as well as update reservations. We have now completed CRU out of CRUD!

Deleting an existing Reservation

Imagine a customer is no longer needs the reservation. We should be able to delete this from the database.
The customer needs to provide their reservationId, so that we can find the reservation in the database and delete it.

For this, we should make use of the HTTP delete request type!

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;

@DeleteMapping("/{reservationId}")
@ResponseStatus(HttpStatus.OK)
public void delete(@PathVariable("reservationId") String reservationId){
    reservationService.deleteReservation(reservationId);
}
  • @DeleteMapping – Tells Spring that this method will accept DELETE requests, which we can use to delete an existing reservation.
  • Assuming the record exists, and the reservation can be deleted, then there will be nothing to return back to the client other than a HTTP status of 200 OK.

If you did get stuck at any point, please checkout branch part-5-implementing-DELETE-delete and compare your code against mine.

Again, let’s now run our service, save something to it, and then delete the reservation:
delete response
Notice how the response is empty, however we get back a HTTP 200 response, indicating that the operation was successful!

…AND WE ARE DONE!! πŸŽ‰πŸŽ‰πŸŽ‰πŸŽ‰πŸŽ‰πŸŽ‰

ALrighty!! We are finally done!!! Congratulations!!
We have successfully implemented a Reservation Management Service, which performs CRUD requests using REST over HTTP. Customers can now finally make and alter reservations as they like, as this is now a full service that can provides business value! You can now go and show this off to all your friends, or use it however you like.
That wasn’t so hard was it!? You can see how much Spring handles for you so that you do not need to keep re-writing the same boiletplate crap code over and over again, so you can focus on getting your service built and into production.
Give yourself a pat on your shoulder, you earnt it!

The finalised codebase can be seen in the FINALISED RMS, and your code should look pretty similar to mine.

So….. What now?? 😳

Currently we have implemented all the happy path situations, when everything goes well. But what happens when you try to perform operations on the database where a customer does not exist? Or what happens if you have duplicate ID’s? these are things that we need to account for which we have not completed as yet.
In my next guide, this is exactly what we are going to be looking at, so that our service can fail more gracefully, rather than spewing lots of errors and making us sad. Check out my Guide to Spring Boot Validation and Error Handing here! πŸ˜‰