A secure RESTful API for managing personal tasks and categories, built with Spring Boot, Spring Data JPA, PostgreSQL, Flyway, and JWT-based authentication.
The API supports user registration and login, task and category CRUD operations, pagination, sorting, filtering, keyword search, and OpenAPI documentation via Swagger UI.
- Project Overview
- Features
- Technologies Used
- Project Structure
- Installation Instructions
- Configuration
- Usage
- Authentication
- API Endpoints
- Error Handling
- Testing
- Contribution Guidelines
- License
Task Management API is a backend service for organizing tasks by category and tracking their status, priority, and due dates.
It is designed around a per-user data model:
- Users can register and log in to obtain JWT access tokens.
- Authenticated users can manage their own tasks and categories.
- Task listing supports pagination, sorting, filtering, and keyword search.
- Database schema changes are managed through Flyway migrations.
- API documentation is exposed through OpenAPI/Swagger.
This project is a solid foundation for a task management web or mobile application.
- User registration with email, username, and password
- User login with JWT token issuance
- Stateless authentication using Bearer tokens
- Role claim included in the JWT payload
- Endpoint protection for tasks and categories
- Create, read, update, and delete tasks
- Associate tasks with categories
- Track task status (
TODO,IN_PROGRESS,DONE) - Track task priority (
LOW,MEDIUM,HIGH,DEFAULT) - Optional due date support
- Automatic timestamps for creation and update
- Per-user task ownership
- Pagination for task listing
- Sorting through Spring Data
Pageable - Filter by:
- status
- priority
- category ID
- Keyword search in title and description
- Create, list, update, and delete categories
- Unique category names per user
- Prevent deletion of categories that are still referenced by tasks
- Per-user category ownership
- Flyway-based schema migration
- Bean Validation for request validation
- RFC 7807-style
ProblemDetailerror responses - Swagger UI / OpenAPI documentation
- Maven Wrapper included for easier local setup
- Java 21
- Spring Boot 4.0.1
- Spring Web
- Spring Data JPA
- Spring Security
- Spring OAuth2 Resource Server
- Spring Validation (Jakarta Validation)
- PostgreSQL
- Flyway
- SpringDoc OpenAPI
- Lombok
- Maven / Maven Wrapper
src/
├─ main/
│ ├─ java/com/laith/taskmanagement/
│ │ ├─ config/
│ │ ├─ controller/
│ │ ├─ dto/
│ │ ├─ exception/
│ │ ├─ mapper/
│ │ ├─ model/
│ │ ├─ repository/
│ │ ├─ security/
│ │ └─ service/
│ └─ resources/
│ ├─ application.properties
│ └─ db/migration/
├─ test/
└─ pom.xml
Before running the project locally, make sure you have:
- Java 21 installed
- PostgreSQL installed and running
- Git installed
- Optional: Maven installed globally
- Not required if you use the included Maven Wrapper
git clone https://github.com/LaithNaimi/task-management-api.git
cd task-management-apiCreate a PostgreSQL database for the application:
CREATE DATABASE taskmanagement;The project uses src/main/resources/application.properties for datasource and JWT settings.
Review and update the values to match your local environment before starting the application.
At minimum, verify:
- database URL
- database username
- database password
- JWT secret
- JWT issuer
- token expiration
Using Maven Wrapper:
./mvnw spring-boot:runOn Windows:
mvnw.cmd spring-boot:runFlyway runs automatically on startup and applies the database schema migrations.
The application currently relies on properties defined in application.properties.
Typical configuration keys include:
| Property | Description |
|---|---|
spring.datasource.url |
PostgreSQL JDBC connection string |
spring.datasource.username |
Database username |
spring.datasource.password |
Database password |
spring.jpa.hibernate.ddl-auto |
Hibernate schema behavior |
spring.flyway.enabled |
Enables Flyway migrations |
spring.flyway.locations |
Migration file location |
jwt.secret |
Secret key used to sign JWT tokens |
jwt.expiration-seconds |
JWT token lifetime |
jwt.issuer |
JWT issuer claim |
For production or team usage, consider moving secrets and environment-specific values into environment variables or external configuration rather than committing them directly in application.properties.
After the application starts:
- Base URL:
http://localhost:8080 - Swagger UI:
http://localhost:8080/swagger-ui/index.html - OpenAPI spec:
http://localhost:8080/v3/api-docs
The security configuration currently allows CORS from:
http://localhost:4200
This is useful if you plan to connect an Angular frontend during local development.
The following endpoints are publicly accessible:
POST /api/auth/registerPOST /api/auth/login- Swagger/OpenAPI endpoints
The following endpoint groups require a valid Bearer token:
/api/tasks/**/api/categories/**
- Register a new user or log in with an existing account.
- Copy the JWT token from the response.
- Send it in the
Authorizationheader:
Authorization: Bearer <your_token>POST /api/auth/register
Content-Type: application/json{
"username": "laith",
"email": "laith@example.com",
"password": "strongPassword123"
}POST /api/auth/login
Content-Type: application/json{
"email": "laith@example.com",
"password": "strongPassword123"
}{
"token": "<jwt_token>",
"tokenType": "Bearer",
"expiresAt": "2026-03-18T12:00:00Z",
"userId": 1,
"role": "USER"
}| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
| POST | /api/auth/register |
Register a new user and receive a JWT | No |
| POST | /api/auth/login |
Authenticate a user and receive a JWT | No |
{
"username": "john",
"email": "john@example.com",
"password": "securePassword"
}{
"email": "john@example.com",
"password": "securePassword"
}| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
| GET | /api/categories |
List all categories for the current user | Yes |
| GET | /api/categories/{id} |
Get a category by ID | Yes |
| POST | /api/categories |
Create a category | Yes |
| PUT | /api/categories/{id} |
Update a category | Yes |
| DELETE | /api/categories/{id} |
Delete a category | Yes |
{
"name": "Work"
}{
"id": 1,
"name": "Work",
"createdAt": "2026-03-18T09:30:00",
"updatedAt": "2026-03-18T09:30:00"
}| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
| GET | /api/tasks |
List tasks with pagination, filtering, and search | Yes |
| GET | /api/tasks/{id} |
Get a task by ID | Yes |
| POST | /api/tasks |
Create a task | Yes |
| PATCH | /api/tasks/{id} |
Partially update a task | Yes |
| DELETE | /api/tasks/{id} |
Delete a task | Yes |
| Parameter | Type | Required | Description |
|---|---|---|---|
status |
enum | No | TODO, IN_PROGRESS, DONE |
priority |
enum | No | LOW, MEDIUM, HIGH, DEFAULT |
categoryId |
long | No | Filter by category |
q |
string | No | Search keyword in title or description |
page |
integer | No | Page index |
size |
integer | No | Page size |
sort |
string | No | Sorting, e.g. dueDate,asc |
{
"title": "Finish README",
"description": "Write project documentation",
"status": "TODO",
"priority": "HIGH",
"categoryId": 1,
"dueDate": "2026-03-20"
}If status and priority are omitted, the entity defaults to:
status = TODOpriority = DEFAULT
{
"title": "Buy groceries",
"description": "Milk, bread, eggs",
"categoryId": 2,
"dueDate": "2026-03-21"
}{
"id": 12,
"title": "Finish README",
"description": "Write project documentation",
"status": "TODO",
"priority": "HIGH",
"categoryId": 1,
"categoryName": "Work",
"dueDate": "2026-03-20",
"createdAt": "2026-03-18T10:00:00",
"updatedAt": "2026-03-18T10:00:00"
}GET /api/tasks?page=0&size=10&sort=dueDate,asc&status=TODO&q=readme
Authorization: Bearer <your_token>Because the endpoint returns a Spring Data Page<TaskResponseDTO>, the response typically includes metadata such as:
{
"content": [
{
"id": 12,
"title": "Finish README",
"description": "Write project documentation",
"status": "TODO",
"priority": "HIGH",
"categoryId": 1,
"categoryName": "Work",
"dueDate": "2026-03-20",
"createdAt": "2026-03-18T10:00:00",
"updatedAt": "2026-03-18T10:00:00"
}
],
"page": {
"size": 10,
"number": 0,
"totalElements": 1,
"totalPages": 1
}
}Note: The exact page wrapper shape depends on the Spring Boot / Spring Data JSON serialization configuration in use.
The API uses structured ProblemDetail responses for many error scenarios, including:
400 Bad Request401 Unauthorized403 Forbidden404 Not Found405 Method Not Allowed409 Conflict500 Internal Server Error
Validation errors may also include a fieldErrors object for request-body validation failures.
{
"type": "about:blank",
"title": "Validation Failed",
"status": 400,
"detail": "Request validation failed",
"timestamp": "2026-03-18T10:15:00Z",
"fieldErrors": {
"title": "size must be between 3 and 100"
}
}At the moment, the repository does not appear to include a dedicated automated test suite or test dependencies.
A future enhancement would be to add:
- unit tests for services and mappers
- controller tests using MockMvc
- repository integration tests
- authentication and authorization tests
Once tests are added, a conventional Maven command would be:
./mvnw testContributions are welcome. To keep the project maintainable, consider the following workflow:
- Fork the repository.
- Create a feature branch:
git checkout -b feature/improve-task-search- Make focused changes with clear commit messages.
- Follow existing coding conventions and package structure.
- Add or update documentation for any API behavior changes.
- Add tests for new functionality whenever a test suite is introduced.
- Open a pull request describing:
- what changed
- why it changed
- how it was tested
- Keep endpoints RESTful and consistent
- Preserve validation and error-response quality
- Avoid breaking existing request/response contracts unnecessarily
- Prefer small, reviewable pull requests
- Update Flyway migrations carefully for schema changes
This repository does not currently include a license file.
Until a license is added by the repository owner, the project should be treated as all rights reserved by default.
If you plan to open-source the project formally, consider adding a standard license such as:
- MIT License
- Apache License 2.0
- GPLv3
If you continue evolving this API, good next steps would be:
- add automated tests
- externalize configuration with environment variables
- add refresh tokens or token revocation strategy
- add Docker / Docker Compose setup
- add API rate limiting and monitoring
- add CI workflows for build and test automation