0

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

📚 Series: Java Backend Zero to Hello 📂 Phân đoạn: Phase 5: Spring Framework & Spring Boot 📖 Nội dung: BÀI 5.6: SPRING BOOT REST API 💡 Khóa học lập trình Backend Java & Spring Boot chuẩn doanh nghiệp từ con số 0.


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 bài học

⭐ Hãy bookmark (clip) lại series để tiện theo dõi các bài học tiếp theo nhé!


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í