4 min read

You may be writing REST like Web services

November 11, 2020

In the previous installment of this series, We discussed about what are the attributes of a RESTful service and why we use them. In this post, We will see how to setup a RESTful service using Spring Boot.

Just a quick RECAP. In order for us to make a RESTful service,

  1. It need to be a web service.
  2. It has to follow stateless programming paradigm.
  3. It needs to assign each resource a URI (in HTTP, we use URL)
  4. Implements HATEOAS.

Given that RESTful services are highly opinionated, Spring ecosystem has managed to build libraries that help building a RESTful services. Most of these components are part of spring-boot-starter-web, spring-boot-starter-hateoas and spring-boot-starter-data-rest. In this post we will see only about the spring boot starter for web. In ideal cases, This starter is enough to build a RESTful service. We will discuss more about the other two in the upcoming installments of this series.

We can add REST like functionalities to a web project using the @RestController annotation along with annotations like @GetMapping, @PostMapping, @DeleteMApping etc for each of the HTTP verbs. I say REST like and not RESTful for a reason. We will see about that further down this post. If you have an understanding of Spring MVC and their annotations, you can easily start working with these annotations.

Take a look at this below example for a shopping cart implementation.

@RestController
@RequestMapping("/carts")
public class CartController {

    private final CartRepository cartRepository;
    private final CartItemRepository cartItemRepository;
    private final ItemRepository itemRepository;
    private final OrderService orderService;

    public CartController(CartRepository cartRepository, CartItemRepository cartItemRepository, ItemRepository itemRepository, OrderService orderService) {
        this.cartRepository = cartRepository;
        this.cartItemRepository = cartItemRepository;
        this.itemRepository = itemRepository;
        this.orderService = orderService;
    }

    @GetMapping("/{cartId}")
    Cart get(@PathVariable Integer cartId) {
        return cartRepository.findById(cartId).orElseThrow(RuntimeException::new);
    }

    @PostMapping("/")
    Cart create(@RequestBody Cart cart) {
        cart.setStatus(CartStatus.NEW);
        return cartRepository.save(cart);
    }

    @PutMapping("/{cartId}")
    Cart update(@PathVariable Integer cartId) {
        Cart cartFromDB = cartRepository.findById(cartId).orElseThrow(RuntimeException::new);
        cartFromDB.setStatus(CartStatus.SUBMITTED);
        //Ignoring validation if the cart content has changed for simplicity.
        orderService.saveOrder(cartFromDB);
        return cartRepository.save(cartFromDB);
    }

    @PostMapping(value = "/{cartId}/items/")
    void update(@RequestBody Item request, @PathVariable Integer cartId) {

        Cart cart = cartRepository.findById(cartId).orElseThrow(RuntimeException::new);
        Item item = itemRepository.findById(request.getId()).orElseThrow(RuntimeException::new);
        CartItem cartItem = new CartItem();
        cartItem.setItem(item);
        cart.addCartItem(cartItem);
        cartRepository.save(cart);

    }

    @DeleteMapping(value = "/{cartId}/items/{itemId}")
    void deleteCartItem(@PathVariable Integer cartId, @PathVariable Integer itemId) {

        Cart cart = cartRepository.findById(cartId).orElseThrow(RuntimeException::new);
        CartItem cartItem = cartItemRepository.findById(itemId).orElseThrow(RuntimeException::new);
        cart.removeCartItem(cartItem);
        cartRepository.save(cart);

    }
}

The above controller is responsible for the APIs that govern a Cart of a shopping site. With these APIs, you can initialize a fresh Cart, Add or remove items to it, Read the current state of the cart and submit the cart for an order if needed. The order process will be initiated once you call @PutMapping at the cart resource with appropriate cartId .

Similarly there are Order and Item controllers which you can find in the github repository linked below. In most of the cases API’s like these are enough. But we are not done yet. Let’s stand back a little and look at our code. The controller seem to use HTTP verbs and the URLs are all hierarchical. But is this REST? No. RESTful services have more attributes that are not satisfied.

As I said earlier, What we have here is a REST like service. Let’s understand a little bit more about what we have implemented.

@RestController

To put it simply, The annotation is just a shortcut for using @Controller and @ResponseBody together. This is clear from the source of the annotation itself.

@RestController = @Controller and @ResponseBody

This means, The following two fragments are the same.

@Controller
@RequestMapping("/orders")
public class OrderController {

    @RequestMapping(value = "/", method = RequestMethod.GET)
    @ResponseBody
    List<Order> getOrders() {
        return Collections.emptyList();
    }
}
@RestController
@RequestMapping("/orders")
public class OrderController {

    @GetMapping("/")
    Order createOrder() {
        return new Order();
    }
}

As you see both Controllers would yield the same results. So why the spring developers named it @RestController? My thought on this is that @Controller can return a view. But @RestController always returns an object as ResponseBody. This object may be serialized into JSON or XML based on the parser in the project libraries classpath.

Annotations like @PostMapping is also more of a shortcut for not specifying the HTTP method. Also I said these annotations are used for building REST like web services. That is due to its leniency towards the concepts of REST.

These annotations doesn’t care if you name your API urls as /getSomething, /acceptOrder etc. So there is a huge chance that you are not doing things the right way. Even if you name all of these URLs proper, then the service is still not restful because the Resources doesn’t follow hyper-media specifications.

So how do we do this properly? This is where spring-boot-starter-hateoas comes into picture. We will see more about this starter in the next post.

About the author
Raja Anbazhagan

I'm a Software Engineer with over 7 years of experience in working with Enterprise Java applications. Lately, I am focused on cloud-based Java applications and serverless technologies. I spend most of my spare time on stackoverflow and this blog.