Skip to content

About

REST API for task management built with Spring Boot

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Task Manager API

CI

Leer en español

A REST API for managing tasks, built with Java 21 and Spring Boot 4. I built it to practise the things a backend job actually asks for: a layered structure, input validation, consistent error responses, tests and a real database running in Docker.

The domain is deliberately small (a task has a title, a description, a completed flag and a creation date) so the focus stays on how the code is organised rather than on business logic.

Stack

  • Java 21, Spring Boot 4 (Web MVC, Data JPA, Validation)
  • H2 in memory for local development and tests
  • PostgreSQL 17 through Docker Compose
  • springdoc-openapi for the Swagger UI
  • JUnit 5, Mockito and MockMvc
  • GitHub Actions to build and run the tests on every push

Endpoints

Method Path What it does Status on success
GET /api/tasks List all tasks 200
GET /api/tasks/{id} Get one task 200
POST /api/tasks Create a task 201
PUT /api/tasks/{id} Replace a task 200
DELETE /api/tasks/{id} Delete a task 204

Errors follow the ProblemDetail format (RFC 9457). Sending an empty title, for example, returns:

{
  "title": "Invalid request",
  "status": 400,
  "detail": "Validation failed",
  "instance": "/api/tasks",
  "errors": { "title": "Title is required" }
}

Asking for a task that does not exist returns a 404 with the same structure.

Running it

You need Java 21. Docker is only required for the PostgreSQL setup.

With H2 (no setup, data is lost on restart):

./mvnw spring-boot:run

With PostgreSQL:

docker compose up -d
./mvnw spring-boot:run -Dspring-boot.run.profiles=postgres

On Windows, use .\mvnw instead of ./mvnw.

Once it is running:

Tests

./mvnw test
  • TaskServiceTest tests the service on its own, with the repository mocked.
  • TaskControllerTest uses @WebMvcTest to check status codes, validation and the error handler without starting the whole application.

Project structure

src/main/java/dev/sergiorascon/taskmanager
├── config       OpenAPI configuration
├── controller   REST endpoints
├── dto          Request and response records
├── exception    Custom exception and global error handler
├── model        JPA entity
├── repository   Spring Data repository
└── service      Business logic

Design decisions

The API never exposes the entity. Requests and responses use their own records (TaskRequest, TaskUpdateRequest, TaskResponse). That way a client cannot send an id or a createdAt and have it saved, and I can change the table without breaking the API.

Validation lives in the DTOs. Format rules (required title, maximum lengths) are Bean Validation annotations, triggered by @Valid in the controller. An invalid request is rejected before it reaches the service, and every failing field is reported at once. Rules that depend on the state of the data would go in the service instead.

completed is a Boolean in the update request, not a boolean. PUT replaces the whole task, so the field has to be there. While testing I found that with a primitive, a missing field ended up as a generic "malformed JSON" error. With Boolean plus @NotNull the client gets a clear message saying which field is missing.

One place for errors. A @RestControllerAdvice turns exceptions into ProblemDetail responses, so every error has the same shape. For malformed JSON I return a fixed message rather than the exception text, which can expose internal details.

Deleting a missing task returns 404. The service looks the task up before deleting it. Otherwise the client would get a 204 for something that never existed.

Constructor injection. Dependencies are final fields received through the constructor. It makes them explicit and lets the unit tests create the service with a mock using a plain new.

Two profiles. The default profile uses H2 so the project runs and the tests pass with nothing installed, which is also what the CI uses. The postgres profile points to the database in Docker. There, ddl-auto=update creates the table, which is fine for development; in production I would manage the schema with migrations.

What I would add next

  • Flyway migrations instead of ddl-auto
  • Pagination and filtering by completed on GET /api/tasks
  • A PATCH endpoint to mark a task as done without sending every field
  • Integration tests against a real PostgreSQL with Testcontainers

Author

Sergio Rascón · LinkedIn · GitHub

About

REST API for task management built with Spring Boot

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages