A backend API for managing projects and tasks, built with Java 21 and Spring Boot
JWT authentication, PostgreSQL persistence, Docker containerisation, and a CI pipeline running on every push. Built from scratch as a deliberate exercise in production thinking.
Quick Start · Authentication · Endpoints · Tests · Documentation
This project started as a way to build real backend muscle memory, not just follow a tutorial. Every layer was written by hand: the security filter chain, the JWT implementation, the exception handling, the Docker setup, and the CI pipeline.
The goal was to understand how a production Spring Boot application actually fits together, and to make decisions that would hold up beyond a learning context.
| Feature | Description |
|---|---|
| 🔐 Authentication | JWT based registration and login. Every endpoint is protected by default |
| 🗂️ Project Management | Full CRUD with a status lifecycle — PLANNED → IN_PROGRESS → COMPLETED |
| ✅ Task Management | Tasks are scoped to projects, with priority levels (LOW, MEDIUM, HIGH) and due dates |
| 🛡️ Error Handling | All errors return structured JSON with appropriate HTTP status codes |
| ✔️ Validation | Jakarta Bean Validation on all request bodies, with readable error messages |
| 📖 API Docs | Swagger UI auto-generated from the codebase, always in sync with the code |
| 🐳 Docker | One command brings up the full stack, application and database included |
| ⚙️ CI | Tests run automatically on every push via GitHub Actions |
| Layer | Technology |
|---|---|
| Language | Java 21 |
| Framework | Spring Boot 4.0.5 |
| Build | Maven |
| Persistence | Spring Data JPA, PostgreSQL 16 |
| Authentication | Spring Security, jjwt 0.12.6 |
| Validation | Jakarta Bean Validation |
| Testing | JUnit 5, Mockito |
| Infrastructure | Docker, docker-compose |
| CI | GitHub Actions |
| Documentation | SpringDoc OpenAPI, Swagger UI |
src/
├── main/java/
│ ├── controller/ # Request mapping and HTTP layer
│ ├── service/ # Business logic
│ ├── repository/ # Data access via Spring Data JPA
│ ├── model/ # JPA entities
│ ├── dto/ # Request and response objects, decoupled from entities
│ ├── security/ # JWT filter, UserDetailsService, security configuration
│ └── exception/ # Global exception handler and custom exception types
└── test/
└── service/ # Unit tests for ProjectService
POST /auth/register Create an account and receive a JWT token
POST /auth/login Log in and receive a JWT token
POST /projects Create a project
GET /projects List all projects
GET /projects/{id} Get a single project
PUT /projects/{id} Update a project
DELETE /projects/{id} Delete a project
POST /projects/{id}/tasks Add a task to a project
GET /projects/{id}/tasks List all tasks on a project
GET /projects/{id}/tasks/{taskId} Get a single task
PUT /projects/{id}/tasks/{taskId} Update a task
DELETE /projects/{id}/tasks/{taskId} Delete a task
Every endpoint except /auth/register and /auth/login requires a valid JWT in the Authorization header.
Register:
curl -X POST http://localhost:8080/auth/register \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "password": "secret123"}'Response:
{
"token": "eyJhbGciOiJIUzI1NiJ9...",
"email": "[email protected]"
}Authenticated request:
curl http://localhost:8080/projects \
-H "Authorization: Bearer YOUR_TOKEN_HERE"Requires Docker Desktop. Brings up the application and a PostgreSQL database with a single command.
git clone https://github.com/Lancelcode/project-manager-api.git
cd project-manager-api
docker-compose up --buildThe API is available at http://localhost:8080.
docker-compose down # stop
docker-compose down -v # stop and clear the databaseRequires Java 21. Runs against an H2 in memory database. Data is lost on restart.
./mvnw spring-boot:runSix unit tests covering the core service layer, all passing, executed automatically on every push to main.
| Test | What it verifies |
|---|---|
getAllProjects_returnsAllProjects |
All projects are retrieved from the repository |
getProjectById_returnsProject_whenFound |
The correct project is returned for a given ID |
getProjectById_throwsException_whenNotFound |
A ProjectNotFoundException is thrown when the ID does not exist |
createProject_savesAndReturnsProject |
The project is persisted and the response is correctly mapped |
deleteProject_deletesProject_whenFound |
The repository delete method is called for a valid project |
updateProject_updatesAndReturnsProject |
Updated fields are saved and returned correctly |
./mvnw testSwagger UI is served at http://localhost:8080/swagger-ui/index.html when the application is running. It reflects the current state of the API at all times and includes a built-in interface for making requests.
Building this end to end taught me more than any tutorial because the mistakes were mine to fix.
- Spring's dependency injection makes sense once you understand the container. Before that it feels like magic, and not the good kind.
- DTOs are not optional. Exposing entities directly from endpoints creates tight coupling that compounds over time.
- A global exception handler with
@ControllerAdviceis one of those things that seems like overhead until the first time you need it. - JWT authentication is straightforward once you understand the filter chain. The tricky part is avoiding circular dependencies in the security configuration, and I hit that problem directly.
- Docker Compose
depends_ondoes not mean "wait until the database is ready." A healthcheck is required. I learned this the hard way. - Writing tests after the fact is harder than writing them first. The code tends not to be structured for testability.
- Write tests before the implementation, not after
- Use Flyway or Liquibase for schema migrations instead of
ddl-auto: update - Add JWT authentication from the start rather than retrofitting it into an existing codebase
- Add pagination from the start, it is messier to retrofit than to build in
Djiby Sow Rebollo
Complete. Authentication, persistence, containerisation, and CI are all in place.
This project sits within a broader series of backend builds. See my profile for the full picture, including PulseDB (database engine from scratch) and protocol level tool rebuilds.