RestController design struggle - Spring Boot REST API

Viewed 611

I'm quite new into REST API development. I have decided to create a Blog application using Spring Boot and I'm really struggling with the design and structure of my app.

Right now my app consists of Post and Comment models and repositories. For both models, I have created service classes (PostService and CommentService). In these classes, I have all the business logic (just simple CRUD right now).

Now I am scratching my head about the design of my @RestControler for Posts. In PostController I have exposed these actions:

@PostMapping("/api/posts/create")
public Post create(@RequestBody Post post) { ... }

@GetMapping("/api/posts")
public List<Post> findAll() { ... }

@GetMapping("/api/posts/{id}")
public Post findById(@PathVariable("id") Long id) { ... }

@PutMapping("/api/posts/{id}")
public Post update(@RequestBody Post post) { ... }

@DeleteMapping("/api/posts/{id}")
public void delete(@PathVariable Long id) { ... }

Now I'm getting to my question. I am wondering what is correct design of adding a Comment to the Post.

  1. Should I expose all CRUD method for Comment using CommentController class and use create method?
  2. Is it ok to add a new method addComment to PostController which will create a new Comment?

In my head adding a Comment to the Post belongs to the Post, but I really don't know.

Could some of give me some advice regarding this matter?

Thanks a lot!

Bye, Tom

2 Answers

If I were you, I'd consider REST Design Principles from the OpenAPI Specification and would follow resource -> sub-resource -> method||identifier pattern. This would probably be the most KISS and clean design for the readability and understanding purposes.

@PostMapping("/api/posts/") //you don't need /create as a separate URI
public Post create(@RequestBody Post post) { ... }

@GetMapping("/api/posts") //This is OK.
public List<Post> findAll() { ... }

@GetMapping("/api/posts/{id}") //OK, however {id} should be optional, hence you can combine this and upper methods in one method.
public Post findById(@PathVariable("id") Long id) { ... }

@PutMapping("/api/posts/{id}") //OK.
public Post update(@RequestBody Post post) { ... }

@DeleteMapping("/api/posts/{id}") //OK.
public void delete(@PathVariable Long id) { ... }

and now, for the comments API design, I would have contain them under posts resource, and would have added these corresponding URIs:

@GetMapping("/api/posts/{id}/comments/{commendId}") //commentId is optional
@PostMapping("/api/posts/{id}/comments/") //you don't need any {commendId} here, just post the payload

and etc. I hope you can come up with method signatures and other method mappings.

You can also see the RESTful naming conventions here

To be honest I don't think that someone can give you the perfect answer here. It is often a personal decision. In common you can say the following about a REST API.

  • the path should only represent your data structure in the database. So for example /api/posts

  • No verbs in your path. What you want to do should be handled by the RequestType (GET, POST, PUT, PATCH, DELETE, etc.)

Now to your case. I can really good understand why you are struggling. I think here are two options:

  1. PostsController

    You say a Comment is always a part of a Post and because of this you design your API like this.

    @PostMapping("/api/posts/{id}/comment")
    public Comment create(@PathVariable Long id), @RequestBody Comment comment) { ... }
    
  2. CommentsController

    You handle Comment as an own object and the Post is just a relation you add to it by attribute.

    @PostMapping("/api/comments")
    public Comment create(@RequestBody Comment comment) { ... }
    

So it is always is it a Subset vs make own Object structure. I think in this case here I would prefer option 2 because I think you want do more operations on this object.

Also you can than design your API in the way that every Controller starts with the object that will be handled /api/OBJECT/xxx/yyy

UPDATE

After reading the comment from @gulliva I think also a good way is to use this URL @PostMapping("/api/posts/{id}/comment") but put it in the CommentsController. I think this is a good way.

Related