A production-ready REST API for agile project management, built with modern Spring Boot and enterprise-grade security practices.
- Overview
- Features
- Tech Stack
- Architecture
- Getting Started
- API Documentation
- Security
- Testing
- Configuration
- Deployment
- Project Structure
- Key Learnings
AgileAPI is a secure, multi-tenant REST API designed for managing agile software development workflows. The application demonstrates modern backend development practices including JWT authentication, layered architecture, comprehensive testing, and production-ready configuration management.
- Multi-tenant Organization Management - Complete data isolation between organizations
- User Management - Secure registration, authentication, and profile management
- Project Management - Create and manage multiple projects per organization
- Sprint Planning - Time-boxed iterations with date validation
- Task Tracking - Granular task management with priorities, statuses, and assignments
- Advanced Filtering - Query tasks by status, priority, and assignee
- JWT Authentication - Stateless, token-based authentication with configurable expiration
- BCrypt Password Hashing - Industry-standard password security
- Organization-based Access Control - Multi-tenant isolation at the database level
- Input Validation - Comprehensive request validation with clear error messages
- Authorization Checks - Every endpoint validates user permissions
- Comprehensive Unit Tests - High test coverage with JUnit 5 and Mockito
- Input Validation - Clear, actionable error messages for API consumers
- Persistent Storage - File-based H2 database (easily swappable for PostgreSQL)
- API Documentation - Detailed endpoint documentation with examples
- Environment Profiles - Separate configurations for dev, test, and production
- Java 21 - Latest LTS version with modern language features
- Spring Boot 3.3.4 - Industry-standard framework for enterprise Java
- Spring Security - Comprehensive security framework
- Spring Data JPA - Object-relational mapping and database abstraction
- Hibernate - JPA implementation with lazy loading and caching
- JJWT 0.11.5 - JSON Web Token creation and validation
- BCrypt - Adaptive password hashing algorithm
- H2 Database - Embedded database (development/demo)
- PostgreSQL-ready - Production configuration template included
- JUnit 5 - Modern testing framework
- Mockito - Mocking framework for unit tests
- MockMvc - Spring MVC testing support
- AssertJ - Fluent assertion library
- Maven 3.9+ - Project management and build automation
┌─────────────────────────────────────────┐
│ Controller Layer │ ← REST endpoints, request/response handling
├─────────────────────────────────────────┤
│ Service Layer │ ← Business logic, orchestration
├─────────────────────────────────────────┤
│ Repository Layer │ ← Data access, JPA repositories
├─────────────────────────────────────────┤
│ Database Layer │ ← H2/PostgreSQL
└─────────────────────────────────────────┘
- Repository Pattern - Abstraction over data access
- DTO Pattern - Separation of API contracts from domain models
- Service Layer Pattern - Centralized business logic
- Dependency Injection - Loose coupling via Spring IoC
- Builder Pattern - JWT token construction
- Strategy Pattern - Pluggable authentication mechanisms
Organization (1) ──── (*) Users
│
│ (1)
│
▼ (*)
Projects (1) ──── (*) Sprints (1) ──── (*) Tasks
│
▼
Assigned to User
1. User Registration → BCrypt hash password → Create organization → Generate JWT
2. User Login → Validate credentials → Generate JWT with claims (userId, orgId)
3. API Request → Extract JWT → Validate signature → Load user → Check org access
4. Data Access → Filter by organization ID → Return only authorized data
- Java 21 or higher (Download)
- Maven 3.9+ (or use included wrapper
./mvnw
) - Git (for cloning the repository)
-
Clone the repository
git clone https://github.com/DavidRBelanger/agileapi.git cd agileapi
-
Run the application
# Using Maven wrapper (recommended) ./mvnw spring-boot:run # Or if you have Maven installed mvn spring-boot:run
-
Verify it's running
curl http://localhost:8080/api/v1/health
-
Test the API (see Testing the API section)
The application will start on http://localhost:8080
# Clean and compile
./mvnw clean compile
# Run tests
./mvnw test
# Package as JAR
./mvnw clean package
# Run the packaged JAR
java -jar target/agileapi-0.0.1-SNAPSHOT.jar
http://localhost:8080/api/v1
Creates a new user and their organization.
curl -X POST http://localhost:8080/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{
"name": "John Doe",
"email": "john@example.com",
"password": "securePassword123",
"organizationName": "Acme Corporation",
"organizationSlug": "acme-corp"
}'
Response:
{
"id": 1,
"name": "John Doe",
"email": "john@example.com",
"organizationId": 1
}
Authenticate and receive a JWT token.
curl -X POST http://localhost:8080/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "john@example.com",
"password": "securePassword123"
}'
Response:
{
"accessToken": "eyJhbGciOiJIUzI1NiJ9...",
"tokenType": "Bearer"
}
Include the token in the Authorization
header for all subsequent requests:
TOKEN="your-jwt-token-here"
curl -X GET http://localhost:8080/api/v1/projects \
-H "Authorization: Bearer $TOKEN"
# List all projects in your organization
GET /api/v1/projects
# Create a new project
POST /api/v1/projects
{
"name": "Website Redesign",
"description": "Complete overhaul of company website"
}
# Get project by ID
GET /api/v1/projects/{id}
# Update project
PATCH /api/v1/projects/{id}
# Delete project
DELETE /api/v1/projects/{id}
# List sprints for a project
GET /api/v1/projects/{projectId}/sprints
# Create a sprint
POST /api/v1/projects/{projectId}/sprints
{
"name": "Sprint 1",
"startDate": "2025-10-01",
"endDate": "2025-10-14"
}
# Get sprint by ID
GET /api/v1/sprints/{sprintId}
# Update sprint
PATCH /api/v1/sprints/{sprintId}
# Delete sprint
DELETE /api/v1/sprints/{sprintId}
# List tasks in a sprint (with optional filters)
GET /api/v1/sprints/{sprintId}/tasks?status=TO_DO&priority=5&assigneeId=1
# Create a task
POST /api/v1/sprints/{sprintId}/tasks
{
"title": "Design homepage mockup",
"description": "Create initial design concepts",
"priority": 5,
"status": "TO_DO",
"assigneeId": 1
}
# Get task by ID
GET /api/v1/tasks/{taskId}
# Update task
PATCH /api/v1/tasks/{taskId}
# Delete task
DELETE /api/v1/tasks/{taskId}
Code | Meaning |
---|---|
200 | Success |
201 | Created |
204 | No Content (successful deletion) |
400 | Bad Request (validation error) |
401 | Unauthorized (missing/invalid token) |
403 | Forbidden (insufficient permissions) |
404 | Not Found |
500 | Internal Server Error |
For detailed documentation with all endpoints, request/response examples, and error codes, see:
- endpoints.md - Complete API reference
- test-endpoints.sh - Automated test script
JWT (JSON Web Tokens)
- Tokens are signed with HS256 algorithm
- Include user ID and organization ID claims
- Default expiration: 24 hours (configurable)
- Stateless authentication (no server-side sessions)
Password Security
- BCrypt hashing with salt (adaptive cost factor)
- Minimum password length: 6 characters
- Never stored in plain text
Multi-tenant Isolation
- Every resource is scoped to an organization
- Users can only access data within their organization
- Authorization checks on every endpoint
- Database-level filtering prevents data leakage
All inputs are validated before processing:
// Example: Sprint validation
{
"name": "Sprint 1", // Required, 3-100 characters
"startDate": "2025-10-01", // Required, valid date
"endDate": "2025-10-14" // Required, must be after start date
}
Validation Rules:
- Email format validation
- Password strength requirements
- String length constraints
- Date range validation
- Priority bounds (1-5)
- Organization slug format (lowercase, numbers, hyphens only)
Password hashing with BCrypt
JWT tokens with expiration
Input validation on all endpoints
SQL injection prevention via JPA/Hibernate
CORS configuration
Stateless session management
Organization-based access control
Secure configuration management (secrets in environment variables)
# Run all tests
./mvnw test
# Run specific test class
./mvnw test -Dtest=ProjectServiceTest
# Run with coverage report
./mvnw test jacoco:report
# Report: target/site/jacoco/index.html
- Unit Tests: Service layer business logic
- Integration Tests: Controller endpoints with MockMvc
- Security Tests: Authentication and authorization flows
Test Statistics:
- 7 test classes
- ~30 test cases
- Coverage of all major workflows
Tests use a separate in-memory H2 database:
- Profile:
test
- Database:
jdbc:h2:mem:testdb
- Auto-cleanup: Fresh database for each test run
@ExtendWith(MockitoExtension.class)
@ActiveProfiles("test")
class ProjectServiceTest {
@Mock
private ProjectRepository projectRepository;
@InjectMocks
private ProjectService projectService;
@Test
void createProject_ShouldSaveAndReturn_WhenValid() {
// Arrange
Project project = new Project();
project.setName("Test Project");
// Act
Project result = projectService.createNewProject(project, user);
// Assert
assertThat(result.getName()).isEqualTo("Test Project");
}
}
For detailed test documentation, see TEST_COVERAGE.md.
The application supports multiple configuration profiles:
Profile | Purpose | Database |
---|---|---|
default | Development | H2 file-based (./data/agiledb ) |
test | Unit testing | H2 in-memory |
prod | Production | PostgreSQL (configurable) |
Development (optional):
# All defaults are provided in application.properties
./mvnw spring-boot:run
Production (required):
# Set JWT secret via environment variable
export JWT_SECRET="your-256-bit-secret-key-here"
export JWT_EXPIRATION=86400000
# Optional: Database configuration
export DB_URL="jdbc:postgresql://localhost:5432/agileapi"
export DB_USERNAME="agileapi_user"
export DB_PASSWORD="secure_password"
# Run with production profile
./mvnw spring-boot:run -Dspring.profiles.active=prod
src/main/resources/
├── application.properties # Default (development) config
├── application-test.properties # Test configuration
└── application-prod.properties.template # Production template (not committed)
Generate a secure JWT secret:
openssl rand -base64 64
Configuration:
# application.properties
jwt.secret=your-secret-key-here
jwt.expiration=86400000 # 24 hours in milliseconds
Development (H2 File-based):
spring.datasource.url=jdbc:h2:file:./data/agiledb
spring.datasource.username=sa
spring.datasource.password=
spring.h2.console.enabled=true
Production (PostgreSQL):
spring.datasource.url=${DB_URL}
spring.datasource.username=${DB_USERNAME}
spring.datasource.password=${DB_PASSWORD}
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.h2.console.enabled=false
Access H2 Console (development only):
URL: http://localhost:8080/h2-console
JDBC URL: jdbc:h2:file:./data/agiledb
Username: sa
Password: (leave empty)
-
Build the JAR
./mvnw clean package
-
Run the JAR
java -jar target/agileapi-0.0.1-SNAPSHOT.jar
- Set
JWT_SECRET
environment variable with strong random key - Configure production database (PostgreSQL recommended)
- Disable H2 console (
spring.h2.console.enabled=false
) - Set
spring.profiles.active=prod
- Review and set
jwt.expiration
appropriately - Configure CORS for your frontend domain
- Set up SSL/TLS (HTTPS)
- Configure logging levels
- Set up monitoring and health checks
Create a Dockerfile
:
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY target/agileapi-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
Build and run:
docker build -t agileapi .
docker run -p 8080:8080 \
-e JWT_SECRET="your-secret" \
-e SPRING_PROFILES_ACTIVE=prod \
agileapi
Compatible with:
- AWS Elastic Beanstalk - Simple Java application deployment
- Heroku - Quick deployment with PostgreSQL add-on
- Google Cloud Run - Containerized deployment
- Azure App Service - Spring Boot native support
- DigitalOcean App Platform - Managed application hosting
agileapi/
├── src/
│ ├── main/
│ │ ├── java/com/dbelanger/spring/agileapi/
│ │ │ ├── auth/ # Authentication controllers & services
│ │ │ │ ├── AuthController.java
│ │ │ │ └── AuthService.java
│ │ │ ├── controller/ # REST controllers
│ │ │ │ ├── ProjectController.java
│ │ │ │ ├── SprintController.java
│ │ │ │ ├── TaskController.java
│ │ │ │ └── UserController.java
│ │ │ ├── dto/ # Data Transfer Objects
│ │ │ │ ├── AuthRegisterRequest.java
│ │ │ │ ├── ProjectRequest.java
│ │ │ │ ├── SprintRequest.java
│ │ │ │ └── TaskRequest.java
│ │ │ ├── model/ # Domain entities (JPA)
│ │ │ │ ├── Organization.java
│ │ │ │ ├── User.java
│ │ │ │ ├── Project.java
│ │ │ │ ├── Sprint.java
│ │ │ │ └── Task.java
│ │ │ ├── repository/ # Data access layer (JPA)
│ │ │ │ ├── OrganizationRepository.java
│ │ │ │ ├── UserRepository.java
│ │ │ │ ├── ProjectRepository.java
│ │ │ │ ├── SprintRepository.java
│ │ │ │ └── TaskRepository.java
│ │ │ ├── security/ # Security configuration
│ │ │ │ ├── JwtService.java
│ │ │ │ ├── JwtAuthenticationFilter.java
│ │ │ │ └── SecurityConfig.java
│ │ │ ├── service/ # Business logic layer
│ │ │ │ ├── OrganizationService.java
│ │ │ │ ├── UserService.java
│ │ │ │ ├── ProjectService.java
│ │ │ │ ├── SprintService.java
│ │ │ │ └── TaskService.java
│ │ │ ├── exception/ # Global exception handling
│ │ │ │ └── GlobalExceptionHandler.java
│ │ │ └── AgileapiApplication.java # Main application class
│ │ └── resources/
│ │ ├── application.properties # Main config
│ │ ├── application-test.properties # Test config
│ │ ├── application-prod.properties.template # Prod template
│ │ └── data.sql # Initial data (optional)
│ └── test/
│ └── java/com/dbelanger/spring/agileapi/
│ ├── auth/ # Auth tests
│ │ ├── AuthServiceTest.java
│ │ └── AuthControllerTest.java
│ ├── controller/ # Controller tests
│ │ ├── ProjectControllerTest.java
│ │ ├── SprintControllerTest.java
│ │ └── TaskControllerTest.java
│ └── service/ # Service tests
│ ├── ProjectServiceTest.java
│ ├── SprintServiceTest.java
│ ├── TaskServiceTest.java
│ └── UserServiceTest.java
├── docs/ # Documentation
│ ├── endpoints.md # API reference
│ ├── models.md # Data models
│ ├── layerstack.md # Architecture
│ ├── TEST_COVERAGE.md # Test documentation
│ ├── PAIN_POINTS_ANALYSIS.md # Code review
│ ├── VALIDATION_AND_JWT_CHANGES.md # Recent changes
│ └── test-endpoints.sh # Automated test script
├── pom.xml # Maven configuration
└── README.md # This file
-
Multi-tenant Data Isolation
- Implemented organization-based filtering at the repository level
- Ensured no cross-tenant data leakage through authorization checks
- Designed flexible service layer that enforces tenant boundaries
-
Stateless Authentication
- Implemented JWT-based authentication without server-side sessions
- Handled token generation, validation, and expiration
- Included custom claims (userId, organizationId) for efficient authorization
-
Input Validation
- Applied Jakarta Bean Validation for automatic request validation
- Implemented custom cross-field validators (e.g., date ranges)
- Provided clear, actionable error messages to API consumers
-
Testing Strategy
- Separated test configuration with in-memory database
- Used mocking for unit tests (Mockito) and integration tests (MockMvc)
- Achieved comprehensive coverage without slow end-to-end tests
-
Configuration Management
- Externalized secrets to environment variables
- Created profile-specific configurations for different environments
- Provided production-ready templates without committing secrets
Why H2 Database?
- Fast setup for demos and local development
- Easy to switch to PostgreSQL for production
- File-based mode provides persistence without external dependencies
Why JWT over Sessions?
- Stateless architecture enables horizontal scaling
- No server-side session storage required
- Mobile-friendly authentication approach
Why Layered Architecture?
- Clear separation of concerns
- Easy to test each layer independently
- Maintainable and extensible codebase
Why DTOs?
- Decouples API contracts from domain models
- Prevents over-fetching and under-fetching
- Enables input validation without polluting domain models
This is a portfolio project, but suggestions and feedback are welcome!
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
David Belanger
- GitHub: @DavidRBelanger
- LinkedIn: linkedin.com/in/davidrbelanger23
- Email: davidrbelanger23@gmail.com
- Built with Spring Boot
- Secured with JJWT
- Tested with JUnit 5 and Mockito
If you're a recruiter or hiring manager reviewing this project:
Want to see it in action? I can provide:
- Live demo deployment
- Video walkthrough
- Detailed architecture discussion
- Pair programming session
Have questions about:
- Design decisions?
- Technical implementation details?
- How I would scale this for production?
- Additional features I'd add with more time?
Feel free to reach out - I'd love to discuss this project!
** If you find this project interesting, please give it a star! **
Made with ☕ and Spring Boot