Cómo crear un Controller REST en Spring Boot (sin vueltas)
Un día te levantas con la idea de crear tu primera API REST. Porque si, es algo que pensamos todos los días, verdad?
Por dónde empiezo? te preguntás, mientras mirás la pantalla buscando algun tutorial en youtube.
Tranqui, puede parecer complicado, pero crear un controller REST básico en Spring Boot es más simple de lo que parece. Y lo mejor de todo: no necesitás base de datos ni configuraciones complejas para empezar. Es más, si te interesa lo podemos ver en otro post.
Que es REST y por qué debería importarte?
Antes de tirarme al código, hablemos de REST. REST (Representational State Transfer) es una forma de diseñar APIs que usa el protocolo HTTP de manera inteligente. En lugar de inventar tu propio sistema de comunicación, aprovechás algo que ya existe y funciona: los verbos HTTP, que encima tienen como muchos años
Pensalo así: si tu aplicación fuera una biblioteca, REST sería como tener reglas claras sobre cómo pedir libros:
- “Mostrame el libro” → GET (leer/obtener)
- “Agregá este libro nuevo” → POST (crear)
- “Reemplazá este libro completamente” → PUT (actualizar todo)
- “Sacá este libro de la biblioteca” → DELETE (eliminar)
Por qué es importante? Porque cuando seguís las convenciones REST:
- Cualquier persona puede entender tu API sin leer documentación extensa
- Las herramientas funcionan automáticamente (Postman, curl, navegadores)
- Escalás más fácil porque seguís estándares probados
- Debugging más rápido porque los códigos HTTP te pueden dar pistas claras de que pasó
La escena del crimen (?)
Imaginemos que tenés que crear una API para manejar libros. Nada complejo: crear, leer, actualizar y borrar. Los famosos CRUD (o ABM en español) de toda la vida.
Pero antes de tirarnos de cabeza al código, necesitamos entender algo fundamental: REST no es solo sobre URLs bonitas. Es sobre usar los verbos HTTP de manera correcta para que quien consuma tu API sepa exactamente qué está pasando.
GET= “Dame información”POST= “Creá algo nuevo”PUT= “Actualizá esto completamente”DELETE= “Eliminá esto”
El modelo básico
Empezamos con algo simple. Un libro con lo mínimo indispensable:
public class Libro {
private Long id;
private String titulo;
private String autor;
private Integer paginas;
// Constructor vacío (Spring lo necesita)
public Libro() {}
// Constructor completo
public Libro(Long id, String titulo, String autor, Integer paginas) {
this.id = id;
this.titulo = titulo;
this.autor = autor;
this.paginas = paginas;
}
// Getters y setters (acá van todos, pero los acorto para no aburrir)
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getTitulo() { return titulo; }
public void setTitulo(String titulo) { this.titulo = titulo; }
public String getAutor() { return autor; }
public void setAutor(String autor) { this.autor = autor; }
public Integer getPaginas() { return paginas; }
public void setPaginas(Integer paginas) { this.paginas = paginas; }
}
Nada del otro mundo. Un POJO (Plain Old Java Object) con todo lo que Spring necesita para hacer su magia de serialización JSON.
Nota: podriamos usar un record, y hasta seria lo mejor, pero para este caso particular usamos un POJO clasico.
El controller
Ahora viene lo bueno. El controller que maneja todo:
@RestController
@RequestMapping("/api/libros")
public class LibroController {
// Simulamos una base de datos en memoria
private List<Libro> libros = new ArrayList<>();
private Long siguienteId = 1L;
// Constructor que agrega algunos libros de ejemplo
public LibroController() {
libros.add(new Libro(siguienteId++, "El Señor de los Anillos", "J.R.R. Tolkien", 1200));
libros.add(new Libro(siguienteId++, "En Busca de Inspiracion", "Nik", 471));
libros.add(new Libro(siguienteId++, "1984", "George Orwell", 328));
}
// GET /api/libros - Obtener todos los libros
@GetMapping
public List<Libro> obtenerTodos() {
return libros;
}
// GET /api/libros/{id} - Obtener un libro específico
@GetMapping("/{id}")
public ResponseEntity<Libro> obtenerPorId(@PathVariable Long id) {
Libro libro = libros.stream()
.filter(l -> l.getId().equals(id))
.findFirst()
.orElse(null);
if (libro == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(libro);
}
// POST /api/libros - Crear un nuevo libro
@PostMapping
public ResponseEntity<Libro> crear(@RequestBody Libro nuevoLibro) {
nuevoLibro.setId(siguienteId++);
libros.add(nuevoLibro);
return ResponseEntity.status(HttpStatus.CREATED).body(nuevoLibro);
}
// PUT /api/libros/{id} - Actualizar un libro completo
@PutMapping("/{id}")
public ResponseEntity<Libro> actualizar(@PathVariable Long id, @RequestBody Libro libroActualizado) {
for (int i = 0; i < libros.size(); i++) {
if (libros.get(i).getId().equals(id)) {
libroActualizado.setId(id);
libros.set(i, libroActualizado);
return ResponseEntity.ok(libroActualizado);
}
}
return ResponseEntity.notFound().build();
}
// DELETE /api/libros/{id} - Eliminar un libro
@DeleteMapping("/{id}")
public ResponseEntity<Void> eliminar(@PathVariable Long id) {
boolean eliminado = libros.removeIf(libro -> libro.getId().equals(id));
if (eliminado) {
return ResponseEntity.noContent().build();
}
return ResponseEntity.notFound().build();
}
}
¿Qué está pasando acá?
Vamos por partes:
@RestController: Le dice a Spring que esta clase maneja requests HTTP y que las respuestas van a ser JSON automáticamente. No necesitás hacer nada más.
@RequestMapping(“/api/libros”): Define la URL base. Todos los endpoints de esta clase van a empezar con /api/libros.
@GetMapping, @PostMapping, etc.: Cada annotation mapea a un verbo HTTP específico. Spring Boot sabe exactamente qué método llamar según el tipo de request.
@PathVariable: Extrae valores de la URL. Si llamás a /api/libros/1, Spring automáticamente pone 1 en el parámetro id.
@RequestBody: Toma el JSON del request y lo convierte automáticamente en un objeto Libro.
ResponseEntity: Te da control total sobre la respuesta HTTP. Podés setear el código de estado, headers, todo lo que necesites según tus necesidades.
Nota: en este caso, al empezar sin integracion con base de datos, no tenemos generadores de IDs, por eso usamos esta version super simplificada, en un entorno productivo es totalmente desaconsejable.
OJO
Probando que funciona
Con este controller podés hacer todas las operaciones básicas:
# Obtener todos los libros
GET http://localhost:8080/api/libros
# Obtener un libro específico
GET http://localhost:8080/api/libros/1
# Crear un nuevo libro
POST http://localhost:8080/api/libros
Content-Type: application/json
{
"titulo": "Don Quijote de la Mancha",
"autor": "Miguel de Cervantes",
"paginas": 863
}
# Actualizar un libro
PUT http://localhost:8080/api/libros/1
Content-Type: application/json
{
"titulo": "El Hobbit",
"autor": "J.R.R. Tolkien",
"paginas": 310
}
# Eliminar un libro
DELETE http://localhost:8080/api/libros/1
Sale con fritas. Spring Boot maneja toda la conversión JSON, el ruteo, los códigos de estado HTTP. Vos solo te concentrás en la lógica de negocio.
Los detalles que marcan la diferencia
Códigos de estado apropiados: No devuelvas siempre 200. Si creás algo, devolvé 201 (CREATED). Si no encontrás algo, devolvé 404 (NOT FOUND). Si eliminás algo, devolvé 204 (NO CONTENT).
ResponseEntity vs return directo: Podés devolver el objeto directamente, pero con ResponseEntity tenés más control. Usa el que te guste mas, pero tené en cuenta que con el control y flexibilidad del ResponseEntity, vas a andar mejor.
Validación de entrada: En este ejemplo básico no validamos nada, pero en el mundo real siempre validá los datos que recibís. @Valid es tu amigo como el balón lo era Oliver (o Tsubasa)
Manejo de errores: Acá uso un approach simple con orElse(null), pero para APIs reales considerá usar Optional de manera más elegante. Es más, como ejercicio planteate crear tu API directamente usando Optional.
Lo que viene después
Este controller funciona perfecto para empezar, pero obviamente le faltan cosas:
- Validaciones de entrada (que el título no esté vacío, etc.)
- Paginación para cuando tengas miles de libros
- Filtros para buscar por autor, título, etc.
- Excepciones customizadas para errores más específicos
- Base de datos (JPA, Hibernate, etc.)
Pero todo eso puede esperar. Primero dominá los fundamentos: verbos HTTP, annotations básicas, y ResponseEntity. El resto lo podes construir sobre esta base.
Para cerrar
Crear un controller REST básico en Spring Boot es pan comido cuando entendés los conceptos fundamentales. Los verbos HTTP no son casuales, cada uno tiene su propósito específico.
La genial está en que Spring Boot hace todo el trabajo pesado: conversión JSON, ruteo, manejo de requests. Vos solo definís qué querés que pase en cada endpoint.
En el otro post vamos a agregar autores y hacer que se relacionen con los libros. Pero por ahora, con esto ya podés crear APIs funcionales que cualquier frontend puede consumir sin problemas.
Deja un comentario