A RESTful task management API built with Spring Boot 4 and Java 17, designed as a portfolio project demonstrating production-ready backend development practices.
| Category | Technology |
|---|---|
| Language | Java 17 |
| Framework | Spring Boot 4.0.6 (Spring Framework 7) |
| Build Tool | Gradle (Groovy DSL) |
| Database | PostgreSQL |
| Migrations | Liquibase |
| ORM | Spring Data JPA + Hibernate 7 |
| Security | Spring Security 7 + JWT (jjwt 0.12.5) |
| Mapping | MapStruct 1.6.3 |
| Boilerplate | Lombok |
| API Docs | SpringDoc OpenAPI 3.0.2 |
| Testing | JUnit 5 + Mockito + AssertJ + Testcontainers |
| Containerization | Docker + Docker Compose |
- JWT Authentication — Stateless auth with Bearer tokens, BCrypt password hashing
- Project Management — Create and manage projects with ownership-based access control
- Task Tracking — Full task lifecycle with status, priority, and assignee support
- Comments — Task-level commenting system
- Input Validation — Bean Validation on all request DTOs
- Structured Error Responses — Global exception handling via
@RestControllerAdvice - Database Migrations — Versioned schema management with Liquibase
- API Documentation — Auto-generated Swagger UI via SpringDoc OpenAPI
The project follows a classic Layered Architecture with strict separation of concerns:
HTTP Request
│
▼
JwtAuthenticationFilter ← validates JWT, sets SecurityContext
│
▼
Spring Security FilterChain ← authorization rules
│
▼
Controller ← HTTP mapping, @Valid, @AuthenticationPrincipal
│
▼
Service ← business logic, @Transactional
│
▼
Repository ← Spring Data JPA
│
▼
PostgreSQL
Key design decisions:
- Controllers never return Entity objects directly — always Response DTOs
- MapStruct handles all Entity ↔ DTO conversions at compile time (zero reflection overhead)
- Business-level authorization (owner checks) lives in the Service layer, not Security config
- All database schema changes are versioned Liquibase changesets — no
ddl-auto: update
src/main/java/com/taskflow/
├── config/ # SecurityConfig, OpenApiConfig
├── controller/ # AuthController, ProjectController, TaskController, CommentController
├── dto/
│ ├── request/ # RegisterRequest, LoginRequest, ProjectRequest, TaskRequest...
│ └── response/ # AuthResponse, ProjectResponse, TaskResponse...
├── entity/ # User, Project, Task, Comment
├── enums/ # Role, TaskStatus, TaskPriority
├── exception/ # ResourceNotFoundException, AccessDeniedException, DuplicateResourceException
├── mapper/ # ProjectMapper, TaskMapper, CommentMapper (MapStruct)
├── repository/ # UserRepository, ProjectRepository, TaskRepository, CommentRepository
├── security/ # JwtService, JwtAuthenticationFilter, UserDetailsImpl, UserDetailsServiceImpl
└── service/
└── impl/ # AuthServiceImpl, ProjectServiceImpl, TaskServiceImpl, CommentServiceImpl
| Method | Endpoint | Access | Description |
|---|---|---|---|
POST |
/api/v1/auth/register |
Public | Register a new user |
POST |
/api/v1/auth/login |
Public | Authenticate and get JWT token |
| Method | Endpoint | Access | Description |
|---|---|---|---|
POST |
/api/v1/projects |
Authenticated | Create a project |
GET |
/api/v1/projects |
Authenticated | Get all projects for current user |
GET |
/api/v1/projects/{id} |
Owner only | Get project by ID |
PUT |
/api/v1/projects/{id} |
Owner only | Update project |
DELETE |
/api/v1/projects/{id} |
Owner only | Delete project |
| Method | Endpoint | Access | Description |
|---|---|---|---|
POST |
/api/v1/projects/{projectId}/tasks |
Project owner | Create a task |
GET |
/api/v1/projects/{projectId}/tasks |
Project member | Get all tasks |
GET |
/api/v1/projects/{projectId}/tasks/{id} |
Project member | Get task by ID |
PUT |
/api/v1/projects/{projectId}/tasks/{id} |
Project member | Update task |
DELETE |
/api/v1/projects/{projectId}/tasks/{id} |
Project owner | Delete task |
| Method | Endpoint | Access | Description |
|---|---|---|---|
POST |
/api/v1/tasks/{taskId}/comments |
Authenticated | Add a comment |
GET |
/api/v1/tasks/{taskId}/comments |
Authenticated | Get comments for a task |
DELETE |
/api/v1/tasks/{taskId}/comments/{id} |
Comment author | Delete a comment |
- Docker + Docker Compose
git clone https://github.com/chizhiks/taskflow-api.git
cd taskflow-api
docker compose up --buildOne command builds the app image, starts PostgreSQL, waits for it to become healthy, then starts the API. No local Java, Gradle, or PostgreSQL installation needed.
The API will be available at http://localhost:8080
Swagger UI: http://localhost:8080/swagger-ui.html
Make sure Docker Desktop is running — integration tests use Testcontainers to spin up a real PostgreSQL instance.
./gradlew testKey properties in src/main/resources/application.yml:
spring:
datasource:
url: jdbc:postgresql://localhost:5432/taskflow
jpa:
hibernate:
ddl-auto: validate # Schema managed by Liquibase, not Hibernate
open-in-view: false
app:
jwt:
secret: ${JWT_SECRET}
expiration-ms: 86400000 # 24 hoursThe project includes three types of tests:
Unit Tests (@ExtendWith(MockitoExtension.class))
AuthServiceTest— 4 tests covering register and login scenariosProjectServiceTest— 6 tests covering CRUD and access control
Integration Tests (@SpringBootTest + Testcontainers)
AuthControllerIntegrationTest— 4 tests covering full HTTP request lifecycle with real PostgreSQL
Context Test
TaskFlowApplicationTests— verifies the Spring context loads correctly
All tests follow the Given-When-Then pattern with AssertJ assertions.
users
├── id (PK)
├── username (UNIQUE)
├── email (UNIQUE)
├── password (BCrypt)
├── first_name
├── last_name
└── role
projects
├── id (PK)
├── name
├── description
└── owner_id (FK → users)
tasks
├── id (PK)
├── title
├── description
├── status (TODO, IN_PROGRESS, DONE)
├── priority (LOW, MEDIUM, HIGH)
├── project_id (FK → projects)
└── assignee_id (FK → users)
comments
├── id (PK)
├── content
├── task_id (FK → tasks)
└── author_id (FK → users)
Schema is managed by 4 Liquibase changesets in src/main/resources/db/changelog/changes/.
- Passwords hashed with BCrypt (adaptive cost factor, automatic salting)
- Authentication via JWT Bearer tokens — fully stateless, no server-side sessions
- Authorization checks at two levels:
- Spring Security
SecurityFilterChain— endpoint-level (authenticated vs public) - Service layer — resource-level (owner/member verification)
- Spring Security
Andrii Chyzhov