Volver a Spring Boot Básico

Construyendo APIs REST con Spring Boot

Spring Boot facilita la creación de servicios web RESTful con @RestController.

Tu Primer Controlador REST

package com.ejemplo.demo.controller; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/api/productos") public class ProductoControlador { private final ProductoServicio productoServicio; public ProductoControlador(ProductoServicio productoServicio) { this.productoServicio = productoServicio; } @GetMapping public List<Producto> obtenerTodos() { return productoServicio.buscarTodos(); } @GetMapping("/{id}") public Producto obtenerPorId(@PathVariable Long id) { return productoServicio.buscarPorId(id); } @PostMapping @ResponseStatus(HttpStatus.CREATED) public Producto crear(@RequestBody @Valid Producto producto) { return productoServicio.guardar(producto); } @PutMapping("/{id}") public Producto actualizar(@PathVariable Long id, @RequestBody @Valid Producto producto) { return productoServicio.actualizar(id, producto); } @DeleteMapping("/{id}") @ResponseStatus(HttpStatus.NO_CONTENT) public void eliminar(@PathVariable Long id) { productoServicio.eliminar(id); } }

Anotaciones de Mapeo de Peticiones

AnotaciónMétodo HTTPUso
@GetMappingGETLeer recursos
@PostMappingPOSTCrear recursos
@PutMappingPUTActualizar recursos (completo)
@PatchMappingPATCHActualización parcial
@DeleteMappingDELETEEliminar recursos

Variables de Ruta y Parámetros

// Variable de ruta: /usuarios/42 @GetMapping("/{id}") public Usuario obtenerUsuario(@PathVariable Long id) { ... } // Parámetros de consulta: /usuarios?pagina=0&tamaño=10 @GetMapping public Page<Usuario> obtenerUsuarios( @RequestParam(defaultValue = "0") int pagina, @RequestParam(defaultValue = "10") int tamaño) { ... }

Cuerpo de Petición y Validación

// DTO con validación public class CrearUsuarioRequest { @NotBlank(message = "El nombre es requerido") private String nombre; @Email(message = "Debe ser un email válido") private String email; @Size(min = 8, message = "La contraseña debe tener al menos 8 caracteres") private String contrasena; } // Controlador con @Valid @PostMapping public UsuarioResponse crearUsuario(@RequestBody @Valid CrearUsuarioRequest request) { return usuarioServicio.crear(request); }

ResponseEntity

@GetMapping("/{id}") public ResponseEntity<Usuario> obtenerUsuario(@PathVariable Long id) { return usuarioServicio.buscarPorId(id) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } @PostMapping public ResponseEntity<Usuario> crearUsuario(@RequestBody @Valid CrearUsuarioRequest req) { Usuario creado = usuarioServicio.crear(req); URI ubicacion = URI.create("/api/usuarios/" + creado.getId()); return ResponseEntity.created(ubicacion).body(creado); }

Manejo Global de Excepciones

@RestControllerAdvice public class ManejadorExcepcionesGlobal { @ExceptionHandler(RecursoNoEncontradoException.class) public ResponseEntity<ErrorResponse> manejarNoEncontrado(RecursoNoEncontradoException ex) { ErrorResponse error = new ErrorResponse("NO_ENCONTRADO", ex.getMessage()); return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error); } @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<ErrorResponse> manejarValidacion(MethodArgumentNotValidException ex) { String mensaje = ex.getBindingResult().getFieldErrors().stream() .map(fe -> fe.getField() + ": " + fe.getDefaultMessage()) .collect(Collectors.joining(", ")); return ResponseEntity.badRequest().body(new ErrorResponse("ERROR_VALIDACION", mensaje)); } }

Excepción Personalizada

public class RecursoNoEncontradoException extends RuntimeException { public RecursoNoEncontradoException(String recurso, Long id) { super(recurso + " no encontrado con id: " + id); } } // Uso en el servicio public Producto buscarPorId(Long id) { return productoRepositorio.findById(id) .orElseThrow(() -> new RecursoNoEncontradoException("Producto", id)); }

Configuración 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") .allowCredentials(true); } }; } }

DTOs vs Entidades

Siempre usa DTOs para separar el contrato de tu API de las entidades internas:

// Entidad (para BD) @Entity public class Usuario { @Id @GeneratedValue private Long id; private String nombre; private String email; private String hashContrasena; // ¡nunca expongas! } // DTO de Respuesta (para API) public record UsuarioResponse(Long id, String nombre, String email) { public static UsuarioResponse desde(Usuario usuario) { return new UsuarioResponse(usuario.getId(), usuario.getNombre(), usuario.getEmail()); } }