Skip to main content

Step 4

Step 4 — REST API design

5 views

Table of contents

REST = resources as URLs, actions as HTTP verbs.

Resource = URL, action = verb

URL GET POST PUT DELETE
/api/posts list create
/api/posts/{id} one replace delete
/api/posts/{id}/comments comments new comment

Resources are plural nouns (/posts). Don't put verbs in URLs (/getPost is wrong).

A controller in one page

@RestController
@RequestMapping("/api/posts")
@RequiredArgsConstructor
public class PostController {
    private final PostService postService;

    @GetMapping
    public List<PostDto> list(@RequestParam(defaultValue = "20") int limit) {
        return postService.findRecent(limit);
    }

    @GetMapping("/{id}")
    public PostDto get(@PathVariable Long id) { return postService.findById(id); }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public PostDto create(@Valid @RequestBody PostCreateRequest request) {
        return postService.create(request);
    }

    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void delete(@PathVariable Long id) { postService.delete(id); }
}

@Valid + @RequestBody validates input.

5 status codes you'll use

  • 200 OK — read / update succeeded
  • 201 Created — created new
  • 204 No Content — deleted, no body
  • 400 Bad Request — invalid input
  • 404 Not Found — resource missing

DTOs vs entities — keep them separate

public record PostCreateRequest(
    @NotBlank @Size(max = 200) String title,
    @NotBlank String body
) {}

public record PostDto(Long id, String title, String body, Instant createdAt) {}

Entity = DB shape. DTO = API shape. Decouple them so changes don't ripple.

Try it

Implement PostService.findRecent(limit) using JpaRepository.findAll(PageRequest.of(0, limit, Sort.by("createdAt").descending())). Test with curl http://localhost:8080/api/posts.

Going deeper

Next

Step 5 adds JWT authentication.

Terms in this content