CRUD Operations in REST

In this article by Ludovic Dewailly, the author of Building a RESTful Web Service with Spring, we will learn how requests to retrieve data from a RESTful endpoint, created to access the rooms in a sample property management system, are typically mapped to the HTTP GET method in RESTful web services. We will expand on this by implementing some of the endpoints to support all the CRUD (Create, Read, Update, Delete) operations.

In this article, we will cover the following topics:

  • Mapping the CRUD operations to the HTTP methods
  • Creating resources
  • Updating resources
  • Deleting resources
  • Testing the RESTful operations
  • Emulating the PUT and DELETE methods

(For more resources related to this topic, see here.)

Mapping the CRUD operations[km1]  to HTTP [km2] [km3] methods

The HTTP 1.1 specification defines the following methods:

  • OPTIONS: This method represents a request for information about the communication options available for the requested URI. This is, typically, not directly leveraged with REST. However, this method can be used as a part of the underlying communication. For example, this method may be used when consuming web services from a web page (as a part of the C[km4] ross-origin resource sharing mechanism).
  • GET: This method retrieves the information identified by the request URI. In the context of the RESTful web services, this method is used to retrieve resources. This is the method used for read operations (the R in CRUD).
  • HEAD: The HEAD requests are semantically identical to the GET requests except the body of the response is not transmitted. This method is useful for obtaining meta-information about resources. Similar to the OPTIONS method, this method is not typically used directly in REST web services.
  • POST: This method is used to instruct the server to accept the entity enclosed in the request as a new resource. The create operations are typically mapped to this HTTP method.
  • PUT: This method requests the server to store the enclosed entity under the request URI. To support the updating of REST resources, this method can be leveraged. As per the HTTP specification, the server can create the resource if the entity does not exist. It is up to the web service designer to decide whether this behavior should be implemented or resource creation should only be handled by POST requests.
  • DELETE: The last operation not yet mapped is for the deletion of resources. The HTTP specification defines a DELETE method that is semantically aligned with the deletion of RESTful resources.
  • TRACE: This method is used to perform actions on web servers. These actions are often aimed to aid development and the testing of HTTP applications. The TRACE requests aren't usually mapped to any particular RESTful operations.
  • CONNECT: This HTTP method is defined to support HTTP tunneling through a proxy server. Since it deals with transport layer concerns, this method has no natural semantic mapping to the RESTful operations.

The RESTful architecture does not mandate the use of HTTP as a communication protocol. Furthermore, even if HTTP is selected as the underlying transport, no provisions are made regarding the mapping of the RESTful operations to the HTTP method. Developers could feasibly support all operations through POST requests.

This being said, the following CRUD to HTTP method mapping is commonly used in REST web services:

Operation

HTTP method

Create

POST

Read

GET

Update

PUT

Delete

DELETE

Our sample web service will use these HTTP methods to support CRUD operations. The rest of this article will illustrate how to build such operations.

Creating r[km5] esources

The inventory component of our sample property management system deals with rooms. If we have already built an endpoint to access the rooms. Let's take a look at how to define an endpoint to create new resources:

@RestController
@RequestMapping("/rooms")
public class RoomsResource {
@RequestMapping(method = RequestMethod.POST)
public ApiResponse addRoom(@RequestBody RoomDTO room) {
   Room newRoom = createRoom(room);
   return new ApiResponse(Status.OK, new RoomDTO(newRoom));
}
}

We've added a new method to our RoomsResource class to handle the creation of new rooms. @RequestMapping is used to map requests to the Java method. Here we map the POST requests to addRoom().

Not specifying a value (that is, path) in @RequestMapping is equivalent to using "/".

We pass the new room as @RequestBody. This annotation instructs Spring to map the body of the incoming web request to the method parameter. Jackson is used here to convert the JSON request body to a Java object.

With this new method, the POSTing requests to http://localhost:8080/rooms with the following JSON body will result in the creation of a new room:

{
name: "Cool Room",
description: "A room that is very cool indeed",
   room_category_id: 1
}

Our new method will return the newly created room:

{
"status":"OK",
"data":{
   "id":2,
   "name":"Cool Room",
   "room_category_id":1,
   "description":"A room that is very cool indeed"
}
}

We can decide to return only the ID of the new resource in response to the resource creation. However, since we may sanitize or otherwise manipulate the data that was sent over, it is a good practice to return the full resource.

Quickly testing endpoints[km6] 

For the purpose of quickly testing our newly created endpoint, let's look at testing the new rooms created using Postman.

Postman (https://www.getpostman.com) is a Google Chrome plugin extension that provides tools to build and test web APIs.

This following screenshot illustrates how Postman can be used to test this endpoint:

In Postman, we specify the URL to send the POST request to http://localhost:8080/rooms, with the "[km7] application/json" content type header and the body of the request. Sending this requesting will result in a new room being created and returned as shown in the following:

We have successfully added a room to our inventory service using Postman. It is equally easy to create incomplete requests to ensure our endpoint performs any necessary sanity checks before persisting data into the database.

JSON versus[km8]  form data

Posting forms is the traditional way of creating new entities on the web and could easily be used to create new RESTful resources. We can change our method to the following:

@RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public ApiResponse addRoom(String name, String description,
long roomCategoryId) {
   Room room = createRoom(name, description, roomCategoryId);
   return new ApiResponse(Status.OK, new RoomDTO(room));
}

The main difference with the previous method is that we tell Spring to map form requests (that is, with application/x-www-form-urlencoded the content type) instead of JSON requests. In addition, rather than expecting an object as a parameter, we receive each field individually.

By default, Spring will use the Java method attribute names to map incoming form inputs. Developers can change this behavior by annotating attribute with @RequestParam("…") to specify the input name.

In situations where the main web service consumer is a web application, using form requests may be more applicable. In most cases, however, the former approach is more in line with RESTful principles and should be favored. Besides, when complex resources are handled, form requests will prove cumbersome to use. From a developer standpoint, it is easier to delegate object mapping to a third-party library such as Jackson.

Now that we have created a new resource, let's see how we can update it.

Updating r[km9] esources

Choosing URI formats is an important part of designing RESTful APIs. As seen previously, rooms are accessed using the /rooms/{roomId} path and created under /rooms. You may recall that as per the HTTP specification, PUT requests can result in creation of entities, if they do not exist. The decision to create new resources on update requests is up to the service designer. It does, however, affect the choice of path to be used for such requests.

Semantically, PUT requests update entities stored under the supplied request URI. This means the update requests should use the same URI as the GET requests: /rooms/{roomId}. However, this approach hinders the ability to support resource creation on update since no room identifier will be available.

The alternative path we can use is /rooms with the room identifier passed in the body of the request. With this approach, the PUT requests can be treated as POST requests when the resource does not contain an identifier.

Given the first approach is semantically more accurate, we will choose not to support resource create on update, and we will use the following path for the PUT requests: /rooms/{roomId}

Update endpoint[km10] 

The following method provides the necessary endpoint to modify the rooms:

@RequestMapping(value = "/{roomId}", method = RequestMethod.PUT)
public ApiResponse updateRoom(@PathVariable long roomId,
@RequestBody RoomDTO updatedRoom) {
   try {
     Room room = updateRoom(updatedRoom);
     return new ApiResponse(Status.OK, new RoomDTO(room));
   } catch (RecordNotFoundException e) {
     return new ApiResponse(Status.ERROR, null, new ApiError(999,
       "No room with ID " + roomId));
}
}

As discussed in the beginning of this article, we map update requests to the HTTP PUT verb. Annotating this method with @RequestMapping(value = "/{roomId}", method = RequestMethod.PUT) instructs Spring to direct the PUT requests here.

The room identifier is part of the path and mapped to the first method parameter. In fashion similar to the resource creation requests, we map the body to our second parameter with the use of @RequestBody.

Testing update requests[km11] 

With Postman, we can quickly create a test case to update the room we created. To do so, we send a PUT request with the following body:

{
id: 2,
name: "Cool Room",
description: "A room that is really very cool indeed",
   room_category_id: 1
}

The resulting response will be the updated room, as shown here:

{
"status": "OK",
"data": {
   "id": 2,
   "name": "Cool Room",
   "room_category_id": 1,
   "description": "A room that is really very cool indeed."
}
}

Should we attempt to update a nonexistent room, the server will generate the following response:

{
"status": "ERROR",
"error": {
   "error_code": 999,
   "description": "No room with ID 3"
}
}

Since we do not support resource creation on update, the server returns an error indicating that the resource cannot be found.

Deleting resources[km12] 

It will come as no surprise that we will use the DELETE verb to delete REST resources. Similarly, the reader will have already figured out that the path to delete requests will be /rooms/{roomId}.

The Java method that deals with room deletion is as follows:

@RequestMapping(value = "/{roomId}", method = RequestMethod.DELETE)
public ApiResponse deleteRoom(@PathVariable long roomId) {
try {
   Room room = inventoryService.getRoom(roomId);
     inventoryService.deleteRoom(room.getId());
     return new ApiResponse(Status.OK, null);
} catch (RecordNotFoundException e) {
     return new ApiResponse(Status.ERROR, null, new ApiError(
       999, "No room with ID " + roomId));
}
}

By declaring the request mapping method to be RequestMethod.DELETE, Spring will make this method handle the DELETE requests.

Since the resource is deleted, returning it in the response would not make a lot of sense. Service designers may choose to return a boolean flag to indicate the resource was successfully deleted. In our case, we leverage the status element of our response to carry this information back to the consumer. The response to deleting a room will be as follows:

{
"status": "OK"
}

With this operation, we have now a full-fledged CRUD API for our Inventory Service. Before we conclude this article, let's discuss how REST developers can deal with situations where not all HTTP verbs can be utilized.

HTTP method override

In certain situations (for example, when the service or its consumers are behind an overzealous corporate firewall, or if the main consumer is a web page), only the GET and POST HTTP methods might be available. In such cases, it is possible to emulate the missing verbs by passing a customer header in the requests.

For example, resource updates can be handle using POST requests by setting a customer header (for example, X-HTTP-Method-Override) to PUT to indicate that we are emulating a PUT request via a POST request. The following method will handle this scenario:

@RequestMapping(value = "/{roomId}", method = RequestMethod.POST,
headers = {"X-HTTP-Method-Override=PUT"})
public ApiResponse updateRoomAsPost(@PathVariable("roomId")
long id, @RequestBody RoomDTO updatedRoom) {
return updateRoom(id, updatedRoom);
}

By setting the headers attribute on the mapping annotation, Spring request routing will intercept the POST requests with our custom header and invoke this method. Normal POST requests will still map to the Java method we had put together to create new rooms.

Summary

In this article, we've performed the implementation of our sample RESTful web service by adding all the CRUD operations necessary to manage the room resources. We've discussed how to organize URIs to best embody the REST principles and looked at how to quickly test endpoints using Postman. Now that we have a fully working component of our system, we can take some time to discuss performance.

Resources for Article:




Further resources on this subject:

  • Introduction to Spring Web Application in No Time[article]
  • Aggregators, File exchange Over FTP/FTPS, Social Integration, and Enterprise Messaging[article]
  • Time Travelling with Spring[article]

  • You've been reading an excerpt of:

    Building a RESTful Web Service with Spring

    Explore Title