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ón | Método HTTP | Uso |
|---|---|---|
@GetMapping | GET | Leer recursos |
@PostMapping | POST | Crear recursos |
@PutMapping | PUT | Actualizar recursos (completo) |
@PatchMapping | PATCH | Actualización parcial |
@DeleteMapping | DELETE | Eliminar 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());
}
}