Project 1: Todo REST API (Full CRUD & Validation)
This is your first full-stack backend project. We will build a Todo API where users can create tasks, mark them as complete, and delete them. This project ties together Phase 1 (Setup), Phase 3 (REST CRUD), Phase 4 (JPA/H2), and Phase 5 (Validation & Exceptions).
The Hyderabad Daily Tiffin Log Analogy:
Imagine the manager of a Tiffin service keeps a daily logbook of all deliveries.
- He writes down a new delivery (POST).
- He reads the list to see what's pending (GET).
- When the delivery is done, he crosses it out (PUT).
- At the end of the month, he tears out old pages (DELETE).
We are building this logbook digitally using Spring Boot.
- @Entity & H2: We map a
TodoJava class to an H2 database table. - @Valid & @NotBlank: The task title cannot be empty. If it is, Spring throws a 400 Bad Request.
- @RestControllerAdvice: We catch validation errors globally and return a clean JSON error message instead of a stack trace.
- ResponseEntity: We return proper HTTP status codes (201 Created, 204 No Content).
1. The Entity & Repository (Todo.java)
package com.devinhyderabad;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import org.springframework.data.jpa.repository.JpaRepository;
@Entity
public class Todo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private boolean completed;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public boolean isCompleted() { return completed; }
public void setCompleted(boolean completed) { this.completed = completed; }
}
interface TodoRepository extends JpaRepository<Todo, Long> {}2. The Controller & Validation (TodoController.java)
package com.devinhyderabad;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/todos")
public class TodoController {
private final TodoRepository repo;
public TodoController(TodoRepository repo) { this.repo = repo; }
@PostMapping
public ResponseEntity<Todo> createTodo(@Valid @RequestBody TodoRequest request) {
Todo todo = new Todo();
todo.setTitle(request.getTitle());
todo.setCompleted(false);
return ResponseEntity.status(HttpStatus.CREATED).body(repo.save(todo));
}
@GetMapping
public List<Todo> getTodos() {
return repo.findAll();
}
@PutMapping("/{id}")
public ResponseEntity<Todo> updateTodo(@PathVariable Long id, @RequestBody Todo updated) {
return repo.findById(id).map(todo -> {
todo.setCompleted(updated.isCompleted());
return ResponseEntity.ok(repo.save(todo));
}).orElse(ResponseEntity.notFound().build());
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteTodo(@PathVariable Long id) {
if (repo.existsById(id)) {
repo.deleteById(id);
return ResponseEntity.noContent().build();
}
return ResponseEntity.notFound().build();
}
}
// DTO for strict validation
class TodoRequest {
@NotBlank(message = "Title cannot be empty")
private String title;
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
}This project demonstrates the exact architecture used in enterprise microservices for simple CRUD modules. By separating the DTO (TodoRequest) from the Entity (Todo), we ensure validation rules don't leak into our database schema, and we protect against mass assignment vulnerabilities.
Interview Note: The use of ResponseEntity is a key detail interviewers look for. Returning ResponseEntity.status(HttpStatus.CREATED) is more explicit than just returning the object and relying on Spring's default status. Always use specific status codes (201 for create, 204 for delete, 404 for not found) to make your API self-documenting.
Key Takeaways
- ✅ CRUD operations map to HTTP verbs: POST (Create), GET (Read), PUT (Update), DELETE (Delete)
- ✅ @Valid + @NotBlank ensures input validation with automatic 400 Bad Request
- ✅ ResponseEntity allows explicit HTTP status codes (201 Created, 204 No Content)
- ✅ DTOs decouple API contracts from database entities, preventing mass assignment attacks
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login