Welcome back! So far, we’ve focused on retrieving data from our RESTful API using GET endpoints. Now, let’s take the next step: creating endpoints for creating, updating, and deleting data. Mastering these operations is critical for building fully functional APIs.
In one of the previous lessons, we covered the concept of resources in REST. In RESTful APIs, a resource can be anything you manage, like a recipe. Each resource is identified by a URI, such as /recipes/123
or /users/john-doe
. So far in this course, we have only focused on reading resources. What other operations can we perform on a resource? We can create, read, update, or delete a resource. These four operations form the abbreviation CRUD.
To perform these operations on a resource, different HTTP methods are used. Each method corresponds to one of the CRUD operations. Spring Boot allows for easy creation of endpoints for each method using special annotations. Here’s a quick overview of the HTTP methods, corresponding Spring annotations, and the expected operations:
HTTP Method | Spring Annotation | Expected Operation |
---|---|---|
GET | @GetMapping | Read/Retrieve |
POST | @PostMapping | Create |
PUT | @PutMapping | Update/Replace |
DELETE | @DeleteMapping | Delete |
PATCH | @PatchMapping | Partial Update |
Although PATCH is included here for completeness, we won’t be practicing it in this course since it’s less frequently used compared to the others.
Let's begin with a quick recap on how to retrieve data. You've already created numerous GET endpoints in the previous exercises:
Java1@RestController 2@RequestMapping("/recipes") 3public class RecipeController { 4 5 @Autowired 6 private RecipeRepository recipeRepository; 7 8 @GetMapping 9 public List<RecipeItem> getAllRecipes() { 10 return recipeRepository.findAll(); 11 } 12 13 @GetMapping("/{id}") 14 public RecipeItem getRecipeById(@PathVariable UUID id) { 15 return recipeRepository.findById(id); 16 } 17}
The first method getAllRecipes
returns a list of all recipes when a GET request is made to /recipes
. The second method getRecipeById
returns a single recipe based on the provided ID when a GET request is made to /recipes/{id}
. The @GetMapping
annotation maps the HTTP GET requests to these methods.
Now that we've reviewed retrieval, let's move on to adding new data using a POST endpoint:
Java1@PostMapping("/recipes") 2public RecipeItem addRecipe(@RequestBody RecipeItem recipeItem) { 3 recipeRepository.save(recipeItem); 4 return recipeItem; 5}
The @PostMapping
annotation maps HTTP POST requests to the addRecipe
method. The @RequestBody
annotation binds the recipe data from the request body to a RecipeItem
object, which is then saved to the repository.
Next, let's explore how to update existing data using a PUT endpoint:
Java1@PutMapping("/recipes/{id}") 2public RecipeItem updateRecipe(@PathVariable UUID id, @RequestBody RecipeItem updatedRecipe) { 3 RecipeItem existingRecipe = recipeRepository.findById(id); 4 if (existingRecipe == null) { 5 throw new RuntimeException("Recipe not found!"); 6 } 7 8 existingRecipe.setTitle(updatedRecipe.getTitle()); 9 existingRecipe.setDescription(updatedRecipe.getDescription()); 10 11 return existingRecipe; 12}
The @PutMapping
annotation maps HTTP PUT requests to the updateRecipe
method. The @PathVariable
annotation extracts the recipe ID from the URI, while the @RequestBody
annotation binds the updated recipe details from the request body.
In the context of a POST or PUT request, the request body is typically a JSON object containing the data you wish to create or update. For instance, when updating a recipe, your request body might look like this:
JSON1{ 2 "title": "Updated Recipe Title", 3 "description": "Updated Recipe Description" 4}
Using the @RequestBody
annotation, this JSON object is automatically converted into a corresponding POJO (Plain Old Java Object). In our Digital Recipe Book example, it will be a RecipeItem
object. This process of converting a request body into a POJO is called deserialization.
Finally, let's discuss deleting data using a DELETE endpoint:
Java1@DeleteMapping("/recipes/{id}") 2public String deleteRecipe(@PathVariable UUID id) { 3 RecipeItem recipe = recipeRepository.findById(id); 4 if (recipe == null) { 5 throw new RuntimeException("Recipe not found!"); 6 } 7 8 recipeRepository.delete(id); 9 return "Recipe deleted!"; 10}
The @DeleteMapping
annotation maps HTTP DELETE requests to the deleteRecipe
method. The @PathVariable
annotation helps to extract the recipe ID from the URI in order to delete the specific recipe.
Let's take a closer look at how common URL prefixes can simplify our code. Right now, our RecipeController
has four endpoints with the /recipes
prefix:
Java1@RestController 2@RequestMapping("/recipes") 3public class RecipeController { 4 5 @Autowired 6 private RecipeRepository recipeRepository; 7 8 @GetMapping 9 public List<RecipeItem> getAllRecipes() { 10 return recipeRepository.findAll(); 11 } 12 13 @PostMapping 14 public RecipeItem addRecipe(@RequestBody RecipeItem recipeItem) { 15 recipeRepository.save(recipeItem); 16 return recipeItem; 17 } 18 19 @GetMapping("/{id}") 20 public RecipeItem getRecipeById(@PathVariable UUID id) { 21 return recipeRepository.findById(id); 22 } 23 24 @PutMapping("/{id}") 25 public RecipeItem updateRecipe(@PathVariable UUID id, @RequestBody RecipeItem updatedRecipe) { 26 RecipeItem existingRecipe = recipeRepository.findById(id); 27 if (existingRecipe == null) { 28 throw new RuntimeException("Recipe not found!"); 29 } 30 31 existingRecipe.setTitle(updatedRecipe.getTitle()); 32 existingRecipe.setDescription(updatedRecipe.getDescription()); 33 34 return existingRecipe; 35 } 36 37 @DeleteMapping("/{id}") 38 public String deleteRecipe(@PathVariable UUID id) { 39 RecipeItem recipe = recipeRepository.findById(id); 40 if (recipe == null) { 41 throw new RuntimeException("Recipe not found!"); 42 } 43 44 recipeRepository.delete(id); 45 return "Recipe deleted!"; 46 } 47}
By defining the /recipes
prefix at the class level using @RequestMapping("/recipes")
, you avoid repeating the prefix for each method.
Before modern annotations like @GetMapping
and @PutMapping
emerged, @RequestMapping
was used to map various HTTP methods. For example:
Java1@RequestMapping(value = "/recipes", method = RequestMethod.GET) 2public List<RecipeItem> getAllRecipes() { 3 return recipeRepository.findAll(); 4}
While we mostly use the more specific annotations nowadays, this approach can still be useful for handling complex mapping scenarios, such as mapping multiple HTTP methods to a single endpoint, but that’s beyond the scope of our lesson.
In this lesson, you’ve learned how to create CRUD endpoints using Spring Boot. We covered how to retrieve, create, update, and delete data in a RESTful API, as well as how to streamline our URL mappings with @RequestMapping
. This foundational knowledge will enable you to build robust and fully functional APIs. In the upcoming practice exercises, you’ll have the opportunity to implement these CRUD operations yourself, reinforcing what you've learned in this lesson.