0

[Java Backend Zero to Hello] BÀI 5.6: SPRING BOOT REST API

Java Backend Zero to Hello

📚 Bài viết thuộc series Java Backend Zero to Hello 📌 Phần: Phase 5: Spring Framework & Spring Boot | Bài 52/86


BÀI 5.6: SPRING BOOT REST API

Mục tiêu

  • Xây dựng REST API với Spring Boot
  • Sử dụng @RestController
  • Xử lý JSON
  • Status code và ResponseEntity

1. @RESTCONTROLLER

@RestController = @Controller + @ResponseBody

@RestController
@RequestMapping("/api/users")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping
    public List<UserDTO> getAll() {
        return userService.findAll();
    }

    @GetMapping("/{id}")
    public UserDTO getById(@PathVariable Long id) {
        return userService.findById(id)
            .orElseThrow(() -> new NotFoundException("User not found"));
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public UserDTO create(@RequestBody @Valid CreateUserRequest request) {
        return userService.create(request);
    }

    @PutMapping("/{id}")
    public UserDTO update(@PathVariable Long id,
                          @RequestBody @Valid UpdateUserRequest request) {
        return userService.update(id, request);
    }

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

2. HTTP STATUS CODE

Code Ý nghĩa
200 OK - Thành công
201 Created - Tạo thành công
204 No Content - Xóa thành công
400 Bad Request - Request không hợp lệ
401 Unauthorized - Chưa xác thực
403 Forbidden - Không có quyền
404 Not Found - Không tìm thấy
409 Conflict - Xung đột
500 Internal Server Error - Lỗi server

Sử dụng ResponseEntity

@GetMapping("/{id}")
public ResponseEntity<UserDTO> getById(@PathVariable Long id) {
    return userService.findById(id)
        .map(user -> ResponseEntity.ok(user))
        .orElse(ResponseEntity.notFound().build());
}

@PostMapping
public ResponseEntity<UserDTO> create(@RequestBody CreateUserRequest request) {
    UserDTO created = userService.create(request);
    URI location = URI.create("/api/users/" + created.getId());
    return ResponseEntity.created(location).body(created);
}

3. JSON HANDLING

3.1 Jackson (mặc định)

Spring Boot dùng Jackson để convert Object ↔ JSON.

3.2 Cấu hình

spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: Asia/Ho_Chi_Minh
    serialization:
      write-dates-as-timestamps: false
      indent-output: true
    deserialization:
      fail-on-unknown-properties: false

3.3 Custom Serialization

@JsonProperty("full_name")
private String name;

@JsonIgnore
private String password;

@JsonFormat(pattern = "dd/MM/yyyy")
private LocalDate birthDate;

@JsonInclude(JsonInclude.Include.NON_NULL)
private String optionalField;

4. DTO PATTERN

Tách layer, không trả entity trực tiếp.

4.1 Request DTO

public record CreateUserRequest(
    @NotBlank String name,
    @Email String email,
    @Min(0) @Max(150) Integer age
) {}

4.2 Response DTO

public record UserResponse(
    Long id,
    String name,
    String email,
    Integer age,
    LocalDateTime createdAt
) {
    public static UserResponse from(User user) {
        return new UserResponse(
            user.getId(),
            user.getName(),
            user.getEmail(),
            user.getAge(),
            user.getCreatedAt()
        );
    }
}

4.3 Sử dụng

@PostMapping
public UserResponse create(@RequestBody CreateUserRequest request) {
    User user = userService.create(request);
    return UserResponse.from(user);
}

5. VALIDATION

5.1 Dependency

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

5.2 Annotation

public record CreateUserRequest(
    @NotBlank(message = "Tên không được rỗng")
    @Size(min = 2, max = 100)
    String name,

    @NotNull
    @Email(message = "Email không hợp lệ")
    String email,

    @Min(value = 0, message = "Tuổi phải >= 0")
    @Max(value = 150, message = "Tuổi phải <= 150")
    Integer age,

    @Pattern(regexp = "^\\d{10}$", message = "SĐT phải 10 chữ số")
    String phone
) {}

5.3 Trong Controller

@PostMapping
public UserResponse create(@RequestBody @Valid CreateUserRequest request) {
    // ...
}

5.4 Custom Validator

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = AdultValidator.class)
public @interface Adult {
    String message() default "Phải >= 18 tuổi";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

public class AdultValidator implements ConstraintValidator<Adult, Integer> {
    @Override
    public boolean isValid(Integer age, ConstraintValidatorContext context) {
        return age != null && age >= 18;
    }
}

6. EXCEPTION HANDLING

6.1 Custom Exception

public class NotFoundException extends RuntimeException {
    public NotFoundException(String message) {
        super(message);
    }
}

6.2 Error Response

public record ErrorResponse(
    int status,
    String message,
    List<String> details,
    LocalDateTime timestamp
) {}

6.3 Global Exception Handler

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(NotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(NotFoundException ex) {
        ErrorResponse error = new ErrorResponse(
            404, ex.getMessage(), null, LocalDateTime.now()
        );
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidation(
            MethodArgumentNotValidException ex) {
        List<String> details = ex.getBindingResult()
            .getFieldErrors()
            .stream()
            .map(fe -> fe.getField() + ": " + fe.getDefaultMessage())
            .collect(Collectors.toList());

        ErrorResponse error = new ErrorResponse(
            400, "Validation failed", details, LocalDateTime.now()
        );
        return ResponseEntity.badRequest().body(error);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGeneric(Exception ex) {
        ErrorResponse error = new ErrorResponse(
            500, "Internal server error", null, LocalDateTime.now()
        );
        return ResponseEntity.internalServerError().body(error);
    }
}

7. CONTENT NEGOTIATION

@GetMapping(value = "/{id}", produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public UserDTO getById(@PathVariable Long id) { ... }

8. CORS

@Configuration
public class CorsConfig {

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/api/**")
                    .allowedOrigins("http://localhost:3000")
                    .allowedMethods("GET", "POST", "PUT", "DELETE")
                    .allowedHeaders("*")
                    .allowCredentials(true);
            }
        };
    }
}

Hoặc trên controller:

@CrossOrigin(origins = "http://localhost:3000")
@RestController
public class UserController { ... }

9. VERSIONING API

// URI Versioning
@GetMapping("/v1/users")
@GetMapping("/v2/users")

// Header Versioning
@GetMapping(value = "/users", headers = "X-API-VERSION=1")

// Parameter Versioning
@GetMapping(value = "/users", params = "version=1")

// Media Type Versioning
@GetMapping(value = "/users", produces = "application/vnd.myapp.v1+json")

10. HATEOAS

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
@GetMapping("/{id}")
public EntityModel<UserResponse> getById(@PathVariable Long id) {
    UserResponse user = userService.findById(id);
    return EntityModel.of(user,
        linkTo(methodOn(UserController.class).getById(id)).withSelfRel(),
        linkTo(methodOn(UserController.class).getAll()).withRel("users")
    );
}

11. BÀI TẬP THỰC HÀNH

Bài 1: User API

Tạo REST API cho User với đầy đủ CRUD + validation.

Bài 2: Error Handling

Implement global exception handler với ErrorResponse.

Bài 3: DTO Pattern

Tách entity và DTO, dùng MapStruct hoặc thủ công.


12. TÓM TẮT

Khái niệm Mô tả
@RestController REST API controller
@RequestBody Nhận JSON
ResponseEntity Trả về với status code
DTO Tách layer
@Valid Validation
@RestControllerAdvice Global exception handler
@JsonProperty Custom JSON field
CORS Cross-Origin

Bài tiếp theo: 5.7 Validation


🧭 Điều Hướng Series

⬅️ Bài trước: BÀI 5.5: SPRING WEB MVC

📋 Lộ trình tổng quan: Xem Toàn Bộ Series

➡️ Bài tiếp theo: BÀI 5.7: VALIDATION


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí