This post gives a high-level overview of creating REST APIs with Spring Boot. The objective is to create a minimalistic REST API say "/posts" API that performs CRUD operations with HTTP verbs POST, PUT, GET, and DELETE.


Define/Design API:

Assume the context path of the app is "/blog". That said, the API would be "/blog/posts".

The first step is to define the supported operations and endpoints behavior.

  1. Create new post

     POST /blog/posts
     content-type: application/json
    
     {
       "title": "srk",
       "content" : "a-z"
     }
    
    • Creates a new blog post.
    • Return HTTP status code
      • On successful creation of new post - 201 - Created
      • If the post already exists in the system - 409 - Conflict
      • For exception or error in the system. - 500 - Internal Server Error
    • Sample 201 Response:

      {
        "id": "1af1828f-9c2b-3312-984d-eb0c2d20d876",
        "title": "srk",
        "content": "a-z",
        "lastModified": "2023-08-25T05:36:14.460532956Z"
      }
      
  2. Retrieve all posts

    GET /blog/posts
    
    • Fetches all the posts in the system and returns an array of posts. Assumption is that the number of posts in the system is very small say <= 10. Let’s not talk about what if there are 100k posts just to keep things simple for now.

    • Return HTTP status codes
      • On successful fetch of all posts from the system - 200 - OK
      • For exception or error in the system. - 500 - Internal Server Error
    • Sample 200 response

       [
         {
           "id": "1af1828f-9c2b-3312-984d-eb0c2d20d876",
           "title": "srk",
           "content": "a-z",
           "lastModified": "2023-08-25T05:34:37.493472824Z"
         }
       ]
      
  3. Get a specific post by id

    GET /blog/posts/{id}
    
    • Retrieves a specific post from the system matching id
    • Return HTTP status codes
      • If the system cannot understand the request - 400 - Bad Request
      • If the resource to be retrieved is not found in the system - 404 - Not Found
      • On successful fetch of specific post from the system - 200 - OK
    • Sample 200 response

      {
       "id": "1af1828f-9c2b-3312-984d-eb0c2d20d876",
       "title": "srk",
       "content": "a-z",
       "lastModified": "2023-08-25T05:36:14.460532956Z"
      }
      
  4. Update a specific post

    PUT /blog/posts/{id}
    Content-Type: application/json
    
    {
      "title": "srk-update",
      "content": "a-z-update"
    }
    
    • Return HTTP status codes
      • If the system cannot understand the request - 400 - Bad Request
      • If the resource to be updated is not found in the system - 404 - Not Found
      • On successful update - 200 - OK
    • Sample 200 Response:

       {
         "id": "1af1828f-9c2b-3312-984d-eb0c2d20d876",
         "title": "srk-update",
         "content": "a-z-update",
         "lastModified": "2023-08-25T06:01:50.222741937Z"
       }
      
  5. Delete a specific post

    DELETE /blog/post/{id}
    
    • Return HTTP status codes
      • If the system cannot understand the request - 400 - Bad Request
      • If the resource to be deleted is not found in the system - 404 - Not Found
      • On successful update - 204 - No Content

Implementation:

The vital part of implementation is Controller which acts as an interface between api consumers and other layers like Service, DAO within the system. So, controller layer is emphasized in this post.

Here is the controller code

package crud;

import java.net.URI;
import java.util.Set;
import java.util.UUID;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author sairaghavak
 */
@RestController
public class BlogController {

  @Autowired private BlogService blogService;
  private static final String INVALID_UUID = "Invalid UUID";


  @PostMapping("/posts")
  public ResponseEntity<Post> createPost(@RequestBody Post post) {
    if (!blogService.getPost(post.getId()).isEmpty()) {
      return ResponseEntity.status(HttpStatus.CONFLICT.value()).build();
    } else if (blogService.createPost(post)) {
      return ResponseEntity.created(URI.create("/blog/posts/" + post.getId()))
      .body(post);
    } else {
      return ResponseEntity.internalServerError().build();
    }
  }

  @GetMapping("/posts")
  public ResponseEntity<Set<Post>> getPosts() {
    Set<Post> posts = blogService.getPosts();
    return ResponseEntity.ok(posts);
  }

  @GetMapping("/posts/{id}")
  public ResponseEntity<?> getPost(@PathVariable("id") String id) {
    if (!blogService.isValidUUID(id)) {
      return ResponseEntity.badRequest().body(INVALID_UUID);
    }

    Post post = blogService.getPost(UUID.fromString(id)).orElse(null);
    if (post == null) {
      return ResponseEntity.notFound().build();
    }
    return ResponseEntity.ok(post);
  }

  @PutMapping("/posts/{id}")
  public ResponseEntity<?> updatePost(@PathVariable("id") String id, 
                                                   @RequestBody Post post) {
    if (!blogService.isValidUUID(id)) {
      return ResponseEntity.badRequest().body(INVALID_UUID);
    }
    Post updatedPost = blogService.updatePost(UUID.fromString(id), post);
    if (updatedPost == null) {
      return ResponseEntity.notFound().build();
    }
    return ResponseEntity.ok(updatedPost);
    
  }

  @DeleteMapping("/posts/{id}")
  public ResponseEntity<?> deletePost(@PathVariable("id") String id) {
    if (!blogService.isValidUUID(id)) {
      return ResponseEntity.badRequest().body(INVALID_UUID);
    }
    boolean isPostDeleted = blogService.deletePost(UUID.fromString(id));
    if (!isPostDeleted) {
      return ResponseEntity.notFound().build();
    }
    return ResponseEntity.noContent().build();
    
  }
}

For complete implementation refer my GitHub repo spring-boot-crud-sample

References:

  1. SpringBoot
  2. Spring Framework web bind Annotations package
  3. Spring Framework HTTP package