A modern, real-time Request for Quotation (RFQ) platform connecting businesses with suppliers
📖 Documentation • 🚀 Quick Start • 📡 API Docs • 🔧 Architecture
- Overview
- Features
- Architecture
- Tech Stack
- Quick Start
- Installation
- API Documentation
- WebSocket Real-time Updates
- Database Schema
- Configuration
- Development
- Testing
- Deployment
- Contributing
- License
Madda is a comprehensive B2B procurement platform that streamlines the Request for Quotation (RFQ) process between buyers and suppliers. Built with a microservices architecture, it offers real-time collaboration, secure authentication, and efficient business communication.
- 🏢 Business Registration with license verification
- 📝 RFQ Management with file attachments
- 💬 Real-time Quotations via WebSocket
- ✅ Quotation Lifecycle (Submit → Accept/Reject/Withdraw)
- 👨💼 Admin Dashboard for user verification
- 🔐 JWT-based Security with role-based access
- 🐳 Docker Containerization for easy deployment
- JWT-based authentication with access and refresh tokens
- Role-based access control (ADMIN, USER, SUPER_ADMIN)
- Secure password hashing with BCrypt
- Email verification with business license upload
- Admin approval workflow for new registrations
- Create, update, and manage RFQs
- Upload multiple files per RFQ (brochures, specifications)
- Set quantity requirements and categories
- Track RFQ status (OPEN, CLOSED, CANCELLED)
- Real-time updates to all connected clients
- Submit quotations in response to RFQs
- Accept/Reject/Withdraw quotation workflow
- One quotation per supplier per RFQ policy
- Real-time notification via WebSocket
- Quotation history and audit trail
- Dashboard with user management
- Approve/reject business registrations
- View verification documents
- Manage user roles and permissions
- System-wide monitoring
- WebSocket connections for instant updates
- STOMP protocol over SockJS
- Automatic reconnection handling
- Topic-based message routing
- Cross-browser synchronization
┌─────────────────────────────────────────────────────────────────┐
│ CLIENT LAYER │
├─────────────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Next.js │ │ Next.js │ │ Next.js │ │
│ │ (Web) │ │ (Web) │ │ (Web) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ API GATEWAY (Port 8080) │
│ Spring Cloud Gateway + Load Balancer │
└─────────────────────────────────────────────────────────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌─────────────────┐ ┌───────────────┐ ┌─────────────────┐
│ Admin Service │ │ User Service │ │ RFQ Service │
│ (Port 8081) │ │ (Port 8082) │ │ (Port 8083) │
│ │ │ │ │ │
│ • Auth/Admin │ │ • User CRUD │ │ • RFQ CRUD │
│ • Bootstrap │ │ • Auth/Login │ │ • File Upload │
│ • Dashboard │ │ • Register │ │ • WebSocket │
└─────────────────┘ └───────────────┘ └─────────────────┘
│
┌─────────────────┐ ┌─────────┴─────────┐
│ Quotation Svc │◄─────────────────│ │
│ (Port 8084) │ │ PostgreSQL DB │
│ │ │ (RFQ Files) │
│ • Quotations │ │ │
│ • WebSocket │ └───────────────────┘
│ • Bid Status │
└─────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ INFRASTRUCTURE SERVICES │
├─────────────────────────────────────────────────────────────────┤
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Eureka Server │ │ PostgreSQL │ │
│ │ (Port 8761) │ │ (Port 5432) │ │
│ │ │ │ │ │
│ │ • Service Reg │ │ • User DB │ │
│ │ • Load Balancing │ │ • RFQ DB │ │
│ │ • Health Checks │ │ • Quotation DB │ │
│ └──────────────────┘ │ • Admin DB │ │
│ └──────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
- Synchronous: REST APIs via API Gateway
- Asynchronous: WebSocket STOMP for real-time updates
- Service Discovery: Eureka for service registration
- Database: Separate PostgreSQL schemas per service
| Technology | Purpose |
|---|---|
| Spring Boot 3.2+ | Microservices framework |
| Spring Cloud Gateway | API Gateway & routing |
| Spring Security | Authentication & authorization |
| Spring WebSocket | Real-time messaging |
| Eureka | Service discovery |
| PostgreSQL | Primary database |
| Flyway | Database migrations |
| JWT | Token-based auth |
| Lombok | Boilerplate reduction |
| MapStruct | DTO mapping |
| Technology | Purpose |
|---|---|
| Next.js 16 | React framework |
| TypeScript | Type safety |
| Tailwind CSS | Styling |
| shadcn/ui | UI components |
| @stomp/stompjs | WebSocket client |
| Zustand | State management |
| React Query | Server state management |
| Axios | HTTP client |
| Lucide React | Icons |
| Technology | Purpose |
|---|---|
| Docker | Containerization |
| Docker Compose | Multi-container orchestration |
| Maven | Build tool |
| npm/pnpm | Package management |
- Java 17+
- Node.js 18+
- Docker & Docker Compose
- Maven 3.8+
git clone https://github.com/sileshidev/Madda.git
cd Maddacd backend
docker compose up -d postgres eureka
cd ..# Build all services
cd backend
mvn clean install -DskipTests
# Start all services with Docker
docker compose up -dcd frontend
npm install --legacy-peer-deps
npm run dev- Frontend: http://localhost:3000
- API Gateway: http://localhost:8080
- Eureka Dashboard: http://localhost:8761
- Swagger UI: http://localhost:8080/swagger-ui (per service)
Admin Bootstrap:
- Email:
admin@madda.et - Password:
AdminPass123!
Test Users:
- User 1:
abebe.bikila@ethiopiantech.et/SecurePass123! - User 2:
selamawit.girma@addisbuilders.et/SecurePass123!
cd backend
docker compose up -d# 1. Start PostgreSQL
docker run -d \
--name madda-postgres \
-e POSTGRES_PASSWORD=postgres \
-p 5432:5432 \
postgres:15-alpine
# 2. Start Eureka
mvn -f discovery-service/pom.xml spring-boot:run
# 3. Start services in order
mvn -f admin-service/pom.xml spring-boot:run
mvn -f user-service/pom.xml spring-boot:run
mvn -f rfq-service/pom.xml spring-boot:run
mvn -f quotation-service/pom.xml spring-boot:run
mvn -f api-gateway/pom.xml spring-boot:runcd frontend
# Install dependencies
npm install --legacy-peer-deps
# Environment setup
cp .env.example .env.local
# Start development server
npm run dev# Database
spring.datasource.url=jdbc:postgresql://localhost:5432/madda
spring.datasource.username=postgres
spring.datasource.password=postgres
# JWT
jwt.secret=your-secret-key-here
jwt.expiration=86400000
# Services
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/NEXT_PUBLIC_API_URL=http://localhost:8080
NEXT_PUBLIC_RFQ_WS_URL=ws://localhost:8083/ws/rfqs
NEXT_PUBLIC_QUOTATION_WS_URL=ws://localhost:8084/ws/quotations| Method | Endpoint | Description |
|---|---|---|
POST |
/api/v1/auth/register |
User registration |
POST |
/api/v1/auth/login |
User login |
POST |
/api/v1/auth/refresh |
Refresh token |
POST |
/admin/auth/bootstrap |
Admin bootstrap |
POST |
/admin/auth/login |
Admin login |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/v1/users/me |
Get current user |
PUT |
/api/v1/users/me |
Update profile |
POST |
/api/v1/users/me/company-logo |
Upload logo |
POST |
/api/v1/users/{id}/verify |
Admin verify user |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/v1/rfqs |
Create RFQ |
GET |
/api/v1/rfqs |
List RFQs |
GET |
/api/v1/rfqs/{id} |
Get RFQ details |
PUT |
/api/v1/rfqs/{id} |
Update RFQ |
DELETE |
/api/v1/rfqs/{id} |
Delete RFQ |
POST |
/api/v1/rfqs/{id}/files |
Upload file |
POST |
/api/v1/rfqs/{id}/cancel |
Cancel RFQ |
POST |
/api/v1/rfqs/{id}/close |
Close RFQ |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/v1/quotations |
Submit quotation |
GET |
/api/v1/quotations |
List quotations |
GET |
/api/v1/quotations/my |
My quotations |
PUT |
/api/v1/quotations/{id} |
Update quotation |
DELETE |
/api/v1/quotations/{id} |
Delete quotation |
POST |
/api/v1/quotations/{id}/withdraw |
Withdraw quotation |
POST |
/api/v1/quotations/{id}/accept |
Accept quotation |
POST |
/api/v1/quotations/{id}/reject |
Reject quotation |
curl -X POST http://localhost:8080/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{
"fullName": "Abebe Bikila",
"email": "abebe.bikila@ethiopiantech.et",
"password": "SecurePass123!",
"phoneNumber": "+251911234567",
"companyName": "Ethiopian Tech Solutions",
"companyType": "TECHNOLOGY",
"businessLicenseNumber": "BLS-2024-12345",
"verificationDocumentUrl": "https://drive.google.com/file/d/..."
}'curl -X POST http://localhost:8080/api/v1/rfqs \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"title": "Server Infrastructure",
"description": "Looking for cloud servers",
"category": "TECHNOLOGY",
"quantity": 10,
"unit": "PCS"
}'curl -X POST http://localhost:8080/api/v1/quotations \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"rfqId": "550e8400-e29b-41d4-a716-446655440000",
"description": "We can provide AWS EC2 instances..."
}'Connect to WebSocket endpoints for real-time updates:
// RFQ Service WebSocket
const rfqClient = new Client({
brokerURL: 'ws://localhost:8083/ws/rfqs',
onConnect: () => {
// Subscribe to public RFQ updates
rfqClient.subscribe('/topic/rfqs/public', (message) => {
console.log('New RFQ:', JSON.parse(message.body));
});
// Subscribe to user-specific updates
rfqClient.subscribe(`/topic/rfqs/user/${userId}`, (message) => {
console.log('Your RFQ updated:', JSON.parse(message.body));
});
}
});
rfqClient.activate();| Topic | Purpose | Message Format |
|---|---|---|
/topic/rfqs/public |
All RFQ updates | {type: 'CREATED'|'UPDATED'|'DELETED', data: {...}} |
/topic/rfqs/user/{id} |
User's RFQ updates | {type: 'CREATED'|'UPDATED'|'DELETED', data: {...}} |
/topic/quotations/public |
All quotation updates | {type: 'CREATED'|'UPDATED'|'STATUS_CHANGED', data: {...}} |
/topic/quotations/user/{id} |
User's quotation updates | {type: 'CREATED'|'UPDATED'|'STATUS_CHANGED', data: {...}} |
/topic/quotations/rfq/{id} |
RFQ-specific quotations | {type: 'CREATED'|'UPDATED', data: {...}} |
interface WebSocketMessage {
type: 'CREATED' | 'UPDATED' | 'DELETED' | 'STATUS_CHANGED';
data: RFQ | Quotation;
timestamp: string;
}┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ users │ │ rfqs │ │ quotations │
├─────────────────┤ ├─────────────────┤ ├─────────────────┤
│ id (PK) │ │ id (PK) │ │ id (PK) │
│ full_name │ │ user_id (FK) │◄──────│ rfq_id (FK) │
│ email │ │ title │ │ user_id (FK) │
│ password │ │ description │ │ description │
│ phone_number │ │ category │ │ status │
│ company_name │ │ quantity │ │ created_at │
│ company_type │ │ unit │ │ updated_at │
│ business_license│ │ status │ └─────────────────┘
│ verified │ │ created_at │
│ role │ │ updated_at │
│ created_at │ └─────────────────┘
│ updated_at │ │
└─────────────────┘ │ ┌─────────────────┐
└──────►│ rfq_files │
├─────────────────┤
│ id (PK) │
│ rfq_id (FK) │
│ file_name │
│ file_url │
│ created_at │
└─────────────────┘
users
- Stores user and business information
- Includes verification status and business license
- Supports ADMIN, USER, SUPER_ADMIN roles
rfqs
- Request for Quotation entities
- Status: OPEN, CLOSED, CANCELLED
- Linked to user (buyer)
quotations
- Supplier responses to RFQs
- Status: PENDING, ACCEPTED, REJECTED, WITHDRAWN
- One quotation per supplier per RFQ
rfq_files
- Attachments for RFQs
- Stores file metadata and URLs
| Service | Port | Description |
|---|---|---|
| API Gateway | 8080 | Entry point for all clients |
| Eureka Server | 8761 | Service registry |
| Admin Service | 8081 | Admin operations |
| User Service | 8082 | User management |
| RFQ Service | 8083 | RFQ operations + WebSocket |
| Quotation Service | 8084 | Quotation operations + WebSocket |
| PostgreSQL | 5432 | Database |
| Frontend | 3000 | Next.js dev server |
# JWT Configuration
jwt:
secret: ${JWT_SECRET:your-256-bit-secret-key-here}
expiration: 86400000 # 24 hours
refresh-expiration: 604800000 # 7 days
# CORS Configuration
cors:
allowed-origins:
- http://localhost:3000
- https://madda.app
allowed-methods: GET, POST, PUT, DELETE, OPTIONS
allowed-headers: Authorization, Content-TypeMadda/
├── backend/
│ ├── api-gateway/ # Spring Cloud Gateway
│ │ └── src/main/
│ │ ├── java/com/madda/gateway/
│ │ └── resources/application.yml
│ ├── discovery-service/ # Eureka Server
│ ├── admin-service/ # Admin operations
│ ├── user-service/ # User management
│ ├── rfq-service/ # RFQ + WebSocket
│ ├── quotation-service/ # Quotations + WebSocket
│ └── docker-compose.yml
├── frontend/
│ ├── src/
│ │ ├── app/ # Next.js pages
│ │ ├── components/ # React components
│ │ ├── hooks/ # Custom hooks (useWebSocket)
│ │ ├── lib/ # Utilities
│ │ └── types/ # TypeScript types
│ ├── public/ # Static assets
│ └── package.json
├── README.md
└── LICENSE
Backend:
- Controller → Service → Repository pattern
- DTOs for request/response
- Entities for database
- Mappers (MapStruct) for conversion
- Global exception handling
- WebSocket controllers for real-time
Frontend:
- App Router (Next.js 13+)
- Server and Client components
- Custom hooks for WebSocket
- Zustand for global state
- React Query for server state
- API client with Axios
# Backend tests
cd backend
mvn test
# Frontend tests
cd frontend
npm test
# E2E tests (if configured)
npm run test:e2e- Create new Spring Boot module
- Add to
pom.xmlparent - Configure Eureka client
- Add API Gateway route
- Update docker-compose.yml
version: '3.8'
services:
postgres:
image: postgres:15-alpine
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
eureka:
build: ./discovery-service
ports:
- "8761:8761"
api-gateway:
build: ./api-gateway
ports:
- "8080:8080"
environment:
- EUREKA_URL=http://eureka:8761/eureka
# ... other servicesCreate .env file:
# Database
DB_PASSWORD=secure_password_here
# JWT
JWT_SECRET=your-super-secret-jwt-key-min-256-bits
# Services
EUREKA_URL=http://eureka:8761/eurekadocker compose -f docker-compose.prod.yml up -dWe welcome contributions! Please follow these steps:
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Commit your changes:
git commit -m 'Add amazing feature' - Push to the branch:
git push origin feature/amazing-feature - Open a Pull Request
- Follow existing code style
- Write meaningful commit messages
- Add tests for new features
- Update documentation
- Ensure all tests pass
This project is licensed under the MIT License - see the LICENSE file for details.
- 📧 Email: support@madda.et
- 💬 Issues: GitHub Issues
- 📖 Wiki: GitHub Wiki
- Spring Boot team for the amazing framework
- Next.js team for the React framework
- shadcn/ui for beautiful components
- The open-source community
Built with ❤️ by the Madda Team