Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Build Spring Boot 3.x REST APIs, microservices, and reactive WebFlux apps with Spring Security 6 and JPA.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/web.md
1# Web Layer - Controllers & REST APIs23## REST Controller Pattern45```java6@RestController7@RequestMapping("/api/v1/users")8@Validated9@RequiredArgsConstructor10public class UserController {11private final UserService userService;1213@GetMapping14public ResponseEntity<Page<UserResponse>> getUsers(15@PageableDefault(size = 20, sort = "createdAt") Pageable pageable) {16Page<UserResponse> users = userService.findAll(pageable);17return ResponseEntity.ok(users);18}1920@GetMapping("/{id}")21public ResponseEntity<UserResponse> getUser(@PathVariable Long id) {22UserResponse user = userService.findById(id);23return ResponseEntity.ok(user);24}2526@PostMapping27public ResponseEntity<UserResponse> createUser(28@Valid @RequestBody UserCreateRequest request) {29UserResponse user = userService.create(request);30URI location = ServletUriComponentsBuilder31.fromCurrentRequest()32.path("/{id}")33.buildAndExpand(user.id())34.toUri();35return ResponseEntity.created(location).body(user);36}3738@PutMapping("/{id}")39public ResponseEntity<UserResponse> updateUser(40@PathVariable Long id,41@Valid @RequestBody UserUpdateRequest request) {42UserResponse user = userService.update(id, request);43return ResponseEntity.ok(user);44}4546@DeleteMapping("/{id}")47@ResponseStatus(HttpStatus.NO_CONTENT)48public void deleteUser(@PathVariable Long id) {49userService.delete(id);50}51}52```5354## Request DTOs with Validation5556```java57public record UserCreateRequest(58@NotBlank(message = "Email is required")59@Email(message = "Email must be valid")60String email,6162@NotBlank(message = "Password is required")63@Size(min = 8, max = 100, message = "Password must be 8-100 characters")64@Pattern(regexp = "^(?=.*[A-Z])(?=.*[a-z])(?=.*\\d).*$",65message = "Password must contain uppercase, lowercase, and digit")66String password,6768@NotBlank(message = "Username is required")69@Size(min = 3, max = 50)70@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "Username must be alphanumeric")71String username,7273@Min(value = 18, message = "Must be at least 18")74@Max(value = 120, message = "Must be at most 120")75Integer age76) {}7778public record UserUpdateRequest(79@Email(message = "Email must be valid")80String email,8182@Size(min = 3, max = 50)83String username84) {}85```8687## Response DTOs8889```java90public record UserResponse(91Long id,92String email,93String username,94Integer age,95Boolean active,96LocalDateTime createdAt,97LocalDateTime updatedAt98) {99public static UserResponse from(User user) {100return new UserResponse(101user.getId(),102user.getEmail(),103user.getUsername(),104user.getAge(),105user.getActive(),106user.getCreatedAt(),107user.getUpdatedAt()108);109}110}111```112113## Global Exception Handling114115```java116@RestControllerAdvice117@Slf4j118public class GlobalExceptionHandler {119120@ExceptionHandler(ResourceNotFoundException.class)121public ResponseEntity<ErrorResponse> handleNotFound(122ResourceNotFoundException ex, WebRequest request) {123log.error("Resource not found: {}", ex.getMessage());124ErrorResponse error = new ErrorResponse(125HttpStatus.NOT_FOUND.value(),126ex.getMessage(),127request.getDescription(false),128LocalDateTime.now()129);130return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);131}132133@ExceptionHandler(MethodArgumentNotValidException.class)134public ResponseEntity<ValidationErrorResponse> handleValidation(135MethodArgumentNotValidException ex) {136Map<String, String> errors = ex.getBindingResult()137.getFieldErrors()138.stream()139.collect(Collectors.toMap(140FieldError::getField,141error -> error.getDefaultMessage() != null142? error.getDefaultMessage()143: "Invalid value"144));145146ValidationErrorResponse response = new ValidationErrorResponse(147HttpStatus.BAD_REQUEST.value(),148"Validation failed",149errors,150LocalDateTime.now()151);152return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);153}154155@ExceptionHandler(DataIntegrityViolationException.class)156public ResponseEntity<ErrorResponse> handleDataIntegrity(157DataIntegrityViolationException ex, WebRequest request) {158log.error("Data integrity violation", ex);159ErrorResponse error = new ErrorResponse(160HttpStatus.CONFLICT.value(),161"Data integrity violation - resource may already exist",162request.getDescription(false),163LocalDateTime.now()164);165return new ResponseEntity<>(error, HttpStatus.CONFLICT);166}167168@ExceptionHandler(Exception.class)169public ResponseEntity<ErrorResponse> handleGlobalException(170Exception ex, WebRequest request) {171log.error("Unexpected error", ex);172ErrorResponse error = new ErrorResponse(173HttpStatus.INTERNAL_SERVER_ERROR.value(),174"An unexpected error occurred",175request.getDescription(false),176LocalDateTime.now()177);178return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);179}180}181182record ErrorResponse(183int status,184String message,185String path,186LocalDateTime timestamp187) {}188189record ValidationErrorResponse(190int status,191String message,192Map<String, String> errors,193LocalDateTime timestamp194) {}195```196197## Custom Validation198199```java200@Target({ElementType.FIELD, ElementType.PARAMETER})201@Retention(RetentionPolicy.RUNTIME)202@Constraint(validatedBy = UniqueEmailValidator.class)203public @interface UniqueEmail {204String message() default "Email already exists";205Class<?>[] groups() default {};206Class<? extends Payload>[] payload() default {};207}208209@Component210@RequiredArgsConstructor211public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {212private final UserRepository userRepository;213214@Override215public boolean isValid(String email, ConstraintValidatorContext context) {216if (email == null) return true;217return !userRepository.existsByEmail(email);218}219}220```221222## WebClient for External APIs223224```java225@Configuration226public class WebClientConfig {227@Bean228public WebClient webClient(WebClient.Builder builder) {229return builder230.baseUrl("https://api.example.com")231.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)232.filter(logRequest())233.build();234}235236private ExchangeFilterFunction logRequest() {237return ExchangeFilterFunction.ofRequestProcessor(request -> {238log.info("Request: {} {}", request.method(), request.url());239return Mono.just(request);240});241}242}243244@Service245@RequiredArgsConstructor246public class ExternalApiService {247private final WebClient webClient;248249public Mono<ExternalDataResponse> fetchData(String id) {250return webClient251.get()252.uri("/data/{id}", id)253.retrieve()254.onStatus(HttpStatusCode::is4xxClientError, response ->255Mono.error(new ResourceNotFoundException("External resource not found")))256.onStatus(HttpStatusCode::is5xxServerError, response ->257Mono.error(new ServiceUnavailableException("External service unavailable")))258.bodyToMono(ExternalDataResponse.class)259.timeout(Duration.ofSeconds(5))260.retry(3);261}262}263```264265## CORS Configuration266267```java268@Configuration269public class WebConfig implements WebMvcConfigurer {270271@Override272public void addCorsMappings(CorsRegistry registry) {273registry.addMapping("/api/**")274.allowedOrigins("http://localhost:3000", "https://example.com")275.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")276.allowedHeaders("*")277.allowCredentials(true)278.maxAge(3600);279}280}281```282283## Quick Reference284285| Annotation | Purpose |286|------------|---------|287| `@RestController` | Marks class as REST controller (combines @Controller + @ResponseBody) |288| `@RequestMapping` | Maps HTTP requests to handler methods |289| `@GetMapping/@PostMapping` | HTTP method-specific mappings |290| `@PathVariable` | Extracts values from URI path |291| `@RequestParam` | Extracts query parameters |292| `@RequestBody` | Binds request body to method parameter |293| `@Valid` | Triggers validation on request body |294| `@RestControllerAdvice` | Global exception handling for REST controllers |295| `@ResponseStatus` | Sets HTTP status code for method |296