A microservices-based fitness tracking application with AI-powered recommendations using Google's Gemini API. Track your activities and receive personalized fitness insights!
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ │ │ │ │ │
│ React Frontend │────▶│ API Gateway │────▶│ Service Registry│
│ (Vite + MUI) │ │ (Spring Cloud) │ │ (Eureka Server) │
│ │ │ │ │ │
└─────────────────┘ └────────┬────────┘ └─────────────────┘
│
┌────────────┼────────────┐
│ │ │
┌─────▼─────┐ ┌───▼─────┐ ┌───▼──────┐
│ User │ │Activity │ │ AI │
│ Service │ │ Service │ │ Service │
│ │ │ │ │ │
└─────┬─────┘ └────┬────┘ └────┬─────┘
│ │ │
┌─────▼─────┐ ┌───▼─────┐ ┌───▼──────┐
│PostgreSQL │ │ MongoDB │ │ MongoDB │
│ │ │ │ │ │
└───────────┘ └─────────┘ └──────────┘
│ │
└────┬───────┘
│
┌─────▼─────┐
│ RabbitMQ │
│ │
└───────────┘
- Java 21 with Spring Boot 3.5.4
- Spring Cloud 2025.0.0 (Latest version)
- Netflix Eureka - Service Discovery
- Spring Cloud Gateway - API Gateway
- Spring Cloud Config - Centralized Configuration
- PostgreSQL - User data storage
- MongoDB - Activity and recommendation storage
- RabbitMQ - Message broker for async communication
- Keycloak - OAuth2/OIDC Authentication
- Google Gemini API - AI-powered recommendations
- React 19 with Vite
- Material-UI v7 - UI components
- Redux Toolkit - State management
- Axios - HTTP client
- React OAuth2 PKCE - OAuth2 authentication flow
git clone https://github.com/yourusername/ai-fitness-tracker.git
cd ai-fitness-trackerCreate a database for the User Service:
CREATE
DATABASE fitness_user_db;Update the username and password in configserver/src/main/resources/config/user-service.yml:
spring:
datasource:
url: jdbc:postgresql://localhost:5432/fitness_user_db
username: your_username
password: your_passwordMongoDB will automatically create databases when services connect. The following databases will be created:
fitnessactivity- For Activity Servicefitnessrecommendations- For AI Service
Run RabbitMQ using Docker:
docker run -d --name rabbitmq \
-p 5672:5672 \
-p 15672:15672 \
rabbitmq:3-managementAccess RabbitMQ Management UI at http://localhost:15672 (guest/guest)
Run Keycloak using Docker:
docker run -d --name keycloak \
-p 8181:8080 \
-e KEYCLOAK_ADMIN=admin \
-e KEYCLOAK_ADMIN_PASSWORD=admin \
quay.io/keycloak/keycloak:latest \
start-dev- Access Keycloak at http://localhost:8181
- Login with admin/admin
- Create a new realm named
fitness-oauth2 - Create a client:
- Client ID:
oauth2-pkce-client - Client Protocol:
openid-connect - Access Type:
public - Valid Redirect URIs:
http://localhost:5173/* - Web Origins:
http://localhost:5173
- Client ID:
- Get your API key from Google AI Studio
- Set environment variables:
export GEMINI_API_KEY="your-api-key-here"
export GEMINI_API_URL="https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent"Or add them to your IDE run configuration.
Import all services as Maven projects in IntelliJ IDEA and start them in this order:
- File → Open → Select the root directory
- Import as Maven project
- Run each service using the Spring Boot run configuration
-
Eureka Server (Port: 8761)
cd eurekaserver ./mvnw spring-boot:runVerify at: http://localhost:8761
-
Config Server (Port: 8888)
cd configserver ./mvnw spring-boot:runVerify config: http://localhost:8888/user-service/default
-
API Gateway (Port: 8080)
cd gateway ./mvnw spring-boot:run -
User Service (Port: 8081)
cd userservice ./mvnw spring-boot:run -
Activity Service (Port: 8082)
cd activityservice ./mvnw spring-boot:run -
AI Service (Port: 8083)
cd aiservice # Remember to set environment variables first! export GEMINI_API_KEY="your-api-key" export GEMINI_API_URL="https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent" ./mvnw spring-boot:run
Note: Wait for each service to fully start before starting the next one. Check Eureka dashboard to ensure all services are registered.
cd fitness-frontend
npm install
npm run devThe application will be available at http://localhost:5173
- Node.js 18+ and npm
- Backend services running (especially API Gateway on port 8080)
- Keycloak configured and running
-
Install Dependencies
cd fitness-frontend npm install -
Configure API URL (Currently Hardcoded)
⚠️ Important: The API URL is currently hardcoded insrc/services/api.js:const API_URL = 'http://localhost:8080/api';
For production or different environments, you'll need to modify this directly in the file.
-
Configure Keycloak
The OAuth2 configuration is in
src/authConfig.js:export const authConfig = { clientId: 'oauth2-pkce-client', authorizationEndpoint: 'http://localhost:8181/realms/fitness-oauth2/protocol/openid-connect/auth', tokenEndpoint: 'http://localhost:8181/realms/fitness-oauth2/protocol/openid-connect/token', redirectUri: 'http://localhost:5173', scope: 'openid profile email offline_access', }
Update these URLs if your Keycloak is running on different ports.
-
Start Development Server
npm run dev
Open http://localhost:5173 in your browser
npm run build
npm run preview # To test production build locallyThe build output will be in the dist/ directory.
-
Authentication
- OAuth2 PKCE flow with Keycloak
- Automatic token refresh
- Logout functionality
-
Activity Management
- ✅ Create new activities (Running, Walking, Cycling)
- ✅ View all activities in a grid layout
- ✅ Click on activities for detailed view
- ✅ Real-time form validation
- ✅ Loading states and error handling
-
AI Recommendations
- ✅ View AI-generated insights for each activity
- ✅ Personalized improvements and suggestions
- ✅ Safety guidelines
- ✅ Powered by Google Gemini
-
UI Features
- ✅ Responsive design (mobile, tablet, desktop)
- ✅ Material-UI v7 components
- ✅ Modern gradient styling
- ✅ Activity type icons
- ✅ Date formatting (Today, Yesterday, etc.)
- ✅ Hover effects and animations
- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
- Page Refresh on Activity Creation: Currently, the page refreshes after adding an activity instead of updating the list dynamically
- No Activity Editing: Activities cannot be edited after creation
- No Activity Deletion: Activities cannot be deleted
- No User Profile Page: User management features not implemented
- No Activity Filters: Cannot filter by date or activity type
- Limited Activity Metrics: Only duration and calories are tracked
- Redux DevTools: Install the browser extension to debug state changes
- Network Tab: Monitor API calls to ensure proper authentication
- Console Errors: Check for any Keycloak or CORS-related errors
fitness-frontend/
├── src/
│ ├── components/
│ │ ├── ActivityDetail.jsx
│ │ ├── ActivityForm.jsx
│ │ └── ActivityList.jsx
│ ├── services/
│ │ └── api.js # API configuration (hardcoded URL here)
│ ├── store/
│ │ ├── authSlice.js # Redux auth state
│ │ └── store.js # Redux store configuration
│ ├── App.jsx # Main app component with routing
│ ├── main.jsx # App entry point
│ └── authConfig.js # OAuth2 configuration
├── public/
├── index.html
└── package.json
-
Login Redirect Loop
- Check Keycloak redirect URI configuration
- Ensure it exactly matches:
http://localhost:5173/*
-
API Connection Refused
- Verify API Gateway is running on port 8080
- Check browser console for CORS errors
- Ensure authentication token is being sent
-
Blank Page After Login
- Check if Redux store has the user data
- Verify token is stored in localStorage
- Look for JavaScript errors in console
-
Style/CSS Issues
- Clear browser cache
- Ensure Material-UI is properly installed
- Check for CSS conflicts in App.css
| Service | URL |
|---|---|
| Frontend | http://localhost:5173 |
| API Gateway | http://localhost:8080 |
| Eureka Dashboard | http://localhost:8761 |
| Config Server | http://localhost:8888 |
| RabbitMQ Management | http://localhost:15672 |
| Keycloak Admin | http://localhost:8181 |
.
├── eurekaserver/ # Service Discovery Server
├── configserver/ # Centralized Configuration
│ └── src/main/resources/config/
│ ├── user-service.yml
│ ├── activity-service.yml
│ ├── ai-service.yml
│ └── api-gateway.yml
├── gateway/ # API Gateway with OAuth2
├── userservice/ # User Management Service
├── activityservice/ # Activity Tracking Service
├── aiservice/ # AI Recommendations Service
└── fitness-frontend/ # React Frontend Application
- User clicks login on the frontend
- Redirected to Keycloak for authentication
- After successful login, redirected back with authorization code
- Frontend exchanges code for tokens using PKCE flow
- Token stored in Redux store and localStorage
- All API requests include the token in Authorization header
- Gateway validates token with Keycloak before routing requests
- Register/Login: Click "Login to Get Started" and authenticate via Keycloak
- Add Activity: Fill in the activity form with type, duration, and calories
- View Activities: See all your logged activities in a grid layout
- Get AI Recommendations: Click on any activity to view AI-powered insights
-
Services not registering with Eureka
- Ensure Eureka Server is running first
- Check if services are using correct Eureka URL
- Wait 30 seconds for registration to complete
-
Database connection errors
- Verify PostgreSQL and MongoDB are running
- Check credentials in config files
- For PostgreSQL:
psql -U your_username -d fitness_user_db - For MongoDB:
mongoshto verify connection
-
Config Server not loading configurations
- Ensure config files are in
configserver/src/main/resources/config/ - File names must match service names (e.g.,
user-service.yml) - Check logs for:
Serving from native locations
- Ensure config files are in
-
Keycloak authentication fails
- Ensure realm name is exactly
fitness-oauth2 - Client ID must be
oauth2-pkce-client - Verify redirect URIs match your frontend URL
- Check CORS settings in Keycloak
- Ensure realm name is exactly
-
RabbitMQ connection refused
- Check if RabbitMQ container is running:
docker ps - Verify credentials (default: guest/guest)
- Ensure ports 5672 and 15672 are not in use
- Check if RabbitMQ container is running:
-
Gemini API errors
- Verify API key is set as environment variable
- Check API quota limits (free tier: 60 requests/minute)
- Ensure API URL is correct for your region
-
Frontend can't connect to backend
- Check if Gateway is running on port 8080
- Verify CORS configuration in Gateway
- Check browser console for specific errors
- Eureka Server: http://localhost:8761/actuator/health
- Config Server: http://localhost:8888/actuator/health
- Gateway: http://localhost:8080/actuator/health
- User Service: http://localhost:8081/actuator/health
- Activity Service: http://localhost:8082/actuator/health
- AI Service: http://localhost:8083/actuator/health
# Check if all services are registered in Eureka
curl http://localhost:8761/eureka/apps
# Test Config Server
curl http://localhost:8888/user-service/default
# Check RabbitMQ queues
docker exec -it rabbitmq rabbitmqctl list_queues
# View service logs
docker logs rabbitmq
docker logs keycloakFor easier setup, you can use Docker Compose for infrastructure:
Create a docker-compose.yml file:
version: '3.8'
services:
postgres:
image: postgres:15
environment:
POSTGRES_DB: fitness_user_db
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
mongodb:
image: mongo:6
ports:
- "27017:27017"
volumes:
- mongo_data:/data/db
rabbitmq:
image: rabbitmq:3-management
ports:
- "5672:5672"
- "15672:15672"
keycloak:
image: quay.io/keycloak/keycloak:latest
environment:
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
ports:
- "8181:8080"
command: start-dev
volumes:
postgres_data:
mongo_data:Run with: docker-compose up -d
All API endpoints are accessed through the API Gateway at http://localhost:8080/api
All endpoints require a valid JWT token in the Authorization header:
Authorization: Bearer <your-jwt-token>
POST /api/users/register
Content-Type: application/json
{
"email": "user@example.com",
"password": "password123",
"firstName": "John",
"lastName": "Doe",
"keycloakId": "optional-keycloak-id"
}Response:
{
"id": "uuid",
"keycloakId": "keycloak-uuid",
"email": "user@example.com",
"firstName": "John",
"lastName": "Doe",
"createdAt": "2024-01-01T10:00:00",
"updatedAt": "2024-01-01T10:00:00"
}Validation:
- Email: Required, must be valid email format
- Password: Required, minimum 6 characters
GET /api/users/{userId}Response:
{
"id": "uuid",
"keycloakId": "keycloak-uuid",
"email": "user@example.com",
"firstName": "John",
"lastName": "Doe",
"createdAt": "2024-01-01T10:00:00",
"updatedAt": "2024-01-01T10:00:00"
}GET /api/users/{userId}/validateResponse:
truePOST /api/activities
Content-Type: application/json
X-User-ID: user-uuid
{
"type": "RUNNING",
"duration": 30,
"caloriesBurned": 300,
"startTime": "2024-01-01T10:00:00",
"additionalMetrics": {
"distance": 5.2,
"avgHeartRate": 145
}
}Response:
{
"id": "activity-uuid",
"userId": "user-uuid",
"type": "RUNNING",
"duration": 30,
"caloriesBurned": 300,
"startTime": "2024-01-01T10:00:00",
"additionalMetric": {
"distance": 5.2,
"avgHeartRate": 145
},
"createdAt": "2024-01-01T10:00:00",
"updatedAt": "2024-01-01T10:00:00"
}Activity Types:
RUNNINGWALKINGCYCLING
GET /api/activities
X-User-ID: user-uuidResponse:
[
{
"id": "activity-uuid",
"userId": "user-uuid",
"type": "RUNNING",
"duration": 30,
"caloriesBurned": 300,
"startTime": "2024-01-01T10:00:00",
"additionalMetric": {},
"createdAt": "2024-01-01T10:00:00",
"updatedAt": "2024-01-01T10:00:00"
}
]GET /api/activities/{activityId}Response: Same as single activity response above
GET /api/recommendations/user/{userId}Response:
[
{
"id": "rec-uuid",
"activityId": "activity-uuid",
"userId": "user-uuid",
"activityType": "RUNNING",
"recommendation": "Great job on your 30-minute run! You maintained a steady pace...",
"improvements": [
"Try incorporating interval training",
"Focus on proper breathing technique"
],
"suggestions": [
"Increase duration by 5 minutes next week",
"Add hill training once a week"
],
"safety": [
"Always warm up before running",
"Stay hydrated"
],
"createdAt": "2024-01-01T10:30:00"
}
]GET /api/recommendations/activity/{activityId}Response: Same as single recommendation above
All endpoints return standard error responses:
{
"timestamp": "2024-01-01T10:00:00",
"status": 400,
"error": "Bad Request",
"message": "Validation failed",
"path": "/api/users/register"
}{
"timestamp": "2024-01-01T10:00:00",
"status": 401,
"error": "Unauthorized",
"message": "Invalid token",
"path": "/api/activities"
}{
"timestamp": "2024-01-01T10:00:00",
"status": 404,
"error": "Not Found",
"message": "Activity not found",
"path": "/api/activities/invalid-id"
}{
"timestamp": "2024-01-01T10:00:00",
"status": 500,
"error": "Internal Server Error",
"message": "An unexpected error occurred",
"path": "/api/activities"
}- Gemini API: 60 requests per minute (free tier)
- No rate limits on other endpoints
The API Gateway allows requests from:
http://localhost:5173(development)- Add production URLs as needed
RabbitMQ events are published when activities are created:
- Exchange:
fitness.exchange - Queue:
activity.queue - Routing Key:
activity.tracking
Decision: Implement a microservices architecture instead of a monolithic application.
Rationale:
- Learning Opportunity: Primary goal was to learn microservices patterns and challenges
- Independent Scaling: Services can scale based on their specific load (AI service might need more resources)
- Technology Diversity: Can use different databases for different services (PostgreSQL for users, MongoDB for activities)
- Fault Isolation: If AI service fails, users can still track activities
- Team Scalability: In a real scenario, different teams could own different services
Trade-offs:
- Increased complexity for a fitness app
- Network latency between services
- Distributed transaction challenges
Decision: Use Spring Cloud ecosystem for microservices infrastructure.
Rationale:
- Mature Ecosystem: Well-documented, production-tested components
- Netflix OSS Integration: Eureka for service discovery is battle-tested
- Spring Boot Integration: Seamless integration with Spring Boot services
- Config Management: Centralized configuration with Spring Cloud Config
- Latest Version: Using 2025.0.0 ensures compatibility with Spring Boot 3.5.4
Decision: Use PostgreSQL for user data storage.
Rationale:
- ACID Compliance: User data requires strong consistency
- Relational Data: Users have structured relationships (roles, profiles)
- JPA Support: Excellent Spring Data JPA integration
- Authentication: Works well with Keycloak user federation
Decision: Use MongoDB for activities and recommendations.
Rationale:
- Flexible Schema: Activities can have varying additional metrics
- Document Model: Natural fit for activity records and AI recommendations
- Time-Series Like: Activities are essentially time-series data
- Scalability: Easy to shard based on user ID or date
- JSON Native: AI responses from Gemini are JSON, stored directly
Decision: Use RabbitMQ for asynchronous communication.
Rationale:
- Decoupling: AI service processes activities asynchronously
- Reliability: Message persistence ensures no activity is lost
- Management UI: Built-in UI for monitoring queues
- Spring Integration: Excellent Spring AMQP support
- Learning: Good introduction to message-driven architecture
Decision: Use Keycloak for authentication instead of building custom auth.
Rationale:
- Industry Standard: Learn production-grade OAuth2/OIDC
- Feature Complete: User management, MFA, social login ready
- Security: Avoid security pitfalls of custom authentication
- Token Management: Handles token issuance, refresh, and validation
- Multi-Realm: Can separate dev/prod environments
Trade-offs:
- Additional infrastructure component
- Learning curve for configuration
- Overhead for simple authentication needs
Decision: Use Google's Gemini API for fitness recommendations.
Rationale:
- Free Tier: Generous free tier (60 requests/minute)
- Quality: High-quality, contextual responses
- No Training Needed: Pre-trained model understands fitness concepts
- Simple Integration: REST API is easy to integrate
- Multi-Modal: Can extend to analyze workout images/videos later
Decision: Use React with MUI for the frontend.
Rationale:
- Component Ecosystem: MUI provides comprehensive components
- Modern React: Using React 19 with latest features
- Design System: Consistent design out of the box
- Customization: Theme system allows brand customization
- Responsive: Mobile-first components
- Type Safety: TypeScript support (can be added later)
Decision: Use Redux Toolkit for state management.
Rationale:
- Best Practices: RTK enforces Redux best practices
- DevTools: Excellent debugging experience
- Auth State: Clean way to manage authentication state
- Scalability: Easy to add more features/slices
- Async Logic: Built-in support for async operations
Decision: Centralize configuration in Spring Cloud Config Server.
Rationale:
- Environment Management: Easy dev/staging/prod configuration
- No Rebuild: Change config without rebuilding services
- Encryption: Supports encrypted values for secrets
- Version Control: Config can be versioned in Git
- Consistency: All services use same config structure
Future Consideration: Could containerize and deploy to K8s once comfortable with current architecture.
| Service | Database | Why |
|---|---|---|
| User Service | PostgreSQL | ACID, relations, JPA |
| Activity Service | MongoDB | Flexible schema, time-series |
| AI Service | MongoDB | JSON storage, recommendations |
| Config Server | File System | Simple, version controlled |
| Eureka Server | In-Memory | Temporary registry data |