A Spring Boot-powered price tracking and notification service that monitors product prices and alerts users when target prices are reached.
- Overview
- Architecture
- Project Structure
- Tech Stack
- Prerequisites
- Getting Started
- Configuration
- API Endpoints
- How It Works
- Key Components
- Message Queue Flow
- Database Schema
- Contributing
- License
PricePulse API is a backend service that allows users to register products they want to track. The system periodically scrapes product prices from the web, stores the price history, and sends email notifications when prices drop below a defined threshold.
Main Features:
- Automated product price scraping
- Price history tracking and storage
- Email alerts on price drops
- Scheduled background price checks
- Asynchronous processing via RabbitMQ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Client / Frontend β
ββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββ
β HTTP REST
ββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββ
β ProductController β
β (REST API Layer) β
ββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββ
β ProductService β
β (Business Logic Layer) β
ββββββββββββββ¬ββββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββ
β β
ββββββββββββββΌβββββββββββ βββββββββββββββΌβββββββββββββββββββββββ
β PriceUpdateScheduler β β RabbitMQ β
β (Scheduled Tasks) βββββΊβ (Message Queue) β
βββββββββββββββββββββββββ βββββββββββββββ¬βββββββββββββββββββββββ
β
βββββββββββββββΌβββββββββββββββββββββββ
β PriceCheckWorker β
β (Async Message Consumer) β
ββββββββ¬βββββββββββββββ¬βββββββββββββββ
β β
ββββββββββββββββΌββββ βββββββββΌβββββββββββββββ
β PriceScraper β β EmailService β
β Service β β (Notifications) β
ββββββββββββββββββββ ββββββββββββββββββββββββ
β
ββββββββββββββββΌβββββββββββββββββββββββββββββββ
β Database (JPA) β
β Product Table β PriceHistory Table β
βββββββββββββββββββββββββββββββββββββββββββββββ
src/
βββ main/
βββ java/
β βββ com/meminksr/pricewatchdogapi/
β βββ config/
β β βββ RabbitMQConfig.java # RabbitMQ queues, exchanges & bindings
β βββ controller/
β β βββ ProductController.java # REST endpoints
β βββ entity/
β β βββ Product.java # Product JPA entity
β β βββ PriceHistory.java # Price history JPA entity
β βββ repository/
β β βββ ProductRepository.java # Product data access
β β βββ PriceHistoryRepository.java # Price history data access
β βββ service/
β β βββ ProductService.java # Core product business logic
β β βββ PriceScraperService.java # Web scraping logic
β β βββ PriceCheckWorker.java # RabbitMQ message consumer
β β βββ PriceUpdateScheduler.java # Cron-based scheduler
β β βββ EmailService.java # Email notification sender
β βββ PriceWatchdogApiApplication.java # Application entry point
βββ resources/
βββ static/ # Static assets
βββ templates/ # Email / view templates
βββ application.properties # App configuration
| Layer | Technology |
|---|---|
| Framework | Spring Boot 3.x |
| Language | Java 17+ |
| ORM | Spring Data JPA / Hibernate |
| Message Broker | RabbitMQ (Spring AMQP) |
| Spring Mail (JavaMailSender) | |
| Scheduling | Spring @Scheduled |
| Web Scraping | Jsoup (or similar) |
| Database (Development) | H2 In-Memory (default) |
| Database (Production) | PostgreSQL / MySQL (user-configured) |
| Build Tool | Maven |
| API Testing | HTTP Client (test.http) |
Before running this project, make sure you have the following installed:
- Java 17+
- Maven 3.8+
- RabbitMQ (running locally or via Docker)
- An SMTP email account (Gmail, etc.)
- PostgreSQL / MySQL (only required for production β H2 is used automatically in development)
Quick start with Docker (RabbitMQ):
docker run -d \
--name rabbitmq \
-p 5672:5672 \
-p 15672:15672 \
rabbitmq:3-managementAccess RabbitMQ dashboard at: http://localhost:15672 (guest / guest)
git clone https://github.com/meminksr/price-watchdog-api.git
cd price-watchdog-apiCopy the template and fill in your own values:
cp src/main/resources/application.properties.example \
src/main/resources/application.propertiesEdit application.properties (see Configuration section below).
mvn clean installmvn spring-boot:runOr run the built JAR directly:
java -jar target/pricewatchdog-api-*.jarThe API will be available at http://localhost:8080.
The project works out of the box with an H2 in-memory database. No extra setup required β tables are created automatically when the application starts.
Default src/main/resources/application.properties:
# ββ Server ββββββββββββββββββββββββββββββββββββββββββ
server.port=8080
# ββ Database (H2 β Development) βββββββββββββββββββββ
spring.datasource.url=jdbc:h2:mem:pricewatchdog
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create-drop
spring.h2.console.enabled=true
# Access H2 console at: http://localhost:8080/h2-console
# ββ RabbitMQ βββββββββββββββββββββββββββββββββββββββββ
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
# ββ Email (SMTP) βββββββββββββββββββββββββββββββββββββ
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=your_email@gmail.com
spring.mail.password=your_app_password
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
# ββ Price Check Scheduler ββββββββββββββββββββββββββββ
price.check.cron=0 0 * * * *What is H2? H2 is a lightweight embedded in-memory database that runs inside the JVM. It's ideal for development and testing, but all data is lost when the application stops. Use a persistent database for production.
If you're deploying the app or need persistent data, remove the H2 configuration and replace it with one of the options below.
1. Add the dependency to pom.xml:
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>2. Update application.properties:
spring.datasource.url=jdbc:postgresql://localhost:5432/pricewatchdog
spring.datasource.username=your_db_user
spring.datasource.password=your_db_password
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=false1. Add the dependency to pom.xml:
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>2. Update application.properties:
spring.datasource.url=jdbc:mysql://localhost:3306/pricewatchdog?useSSL=false&serverTimezone=UTC
spring.datasource.username=your_db_user
spring.datasource.password=your_db_password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=falseDon't want to install PostgreSQL locally? Spin it up in seconds with Docker:
docker run -d \
--name postgres \
-e POSTGRES_DB=pricewatchdog \
-e POSTGRES_USER=your_db_user \
-e POSTGRES_PASSWORD=your_db_password \
-p 5432:5432 \
postgres:15Security Note: Never commit credentials to version control. Use environment variables or a secrets manager in production.
# Example: using environment variables spring.datasource.password=${DB_PASSWORD} spring.mail.password=${MAIL_PASSWORD}
The API is fully documented using OpenAPI (Swagger). You can access the interactive API documentation and test all endpoints directly from your browser.
URL: http://localhost:8080/swagger-ui/index.html
| Method | Endpoint | Description |
|---|---|---|
GET |
/products |
List all tracked products |
GET |
/products/{id} |
Get a single product by ID |
POST |
/products |
Add a new product to track |
PUT |
/products/{id} |
Update product details / target price |
DELETE |
/products/{id} |
Remove a product from tracking |
| Method | Endpoint | Description |
|---|---|---|
GET |
/products/{id}/price-history |
Get full price history for a product |
GET |
/products/{id}/price-history/latest |
Get the most recent price entry |
Add a product to track:
POST /api/products
Content-Type: application/json
{
"name": "Sony WH-1000XM5",
"url": "https://example.com/product/sony-wh1000xm5",
"targetPrice": 250.00,
"notificationEmail": "user@example.com"
}Response:
{
"id": 1,
"name": "Sony WH-1000XM5",
"url": "https://example.com/product/sony-wh1000xm5",
"currentPrice": 299.99,
"targetPrice": 250.00,
"notificationEmail": "user@example.com",
"createdAt": "2025-05-13T10:00:00"
}Get price history:
GET /api/products/1/price-history[
{
"price": 320.00,
"checkedAt": "2025-05-10T08:00:00"
},
{
"price": 299.99,
"checkedAt": "2025-05-11T08:00:00"
},
{
"price": 280.00,
"checkedAt": "2025-05-12T08:00:00"
}
]1. User registers a product URL with a target price
β
βΌ
2. PriceUpdateScheduler triggers on schedule (e.g. every hour)
β
βΌ
3. Scheduler publishes a price-check message to RabbitMQ
β
βΌ
4. PriceCheckWorker consumes the message asynchronously
β
βΌ
5. PriceScraperService fetches & parses the product page
β
βΌ
6. New price is saved to PriceHistory table
β
βββ [Price β€ Target] βββΊ EmailService sends alert to user
β
βββ [Price > Target] βββΊ No action, wait for next cycle
Declares the exchange, queue, and binding used by the price-check workflow. Enables asynchronous, decoupled communication between the scheduler and worker.
Exposes REST endpoints for CRUD operations on tracked products and their price histories. Delegates business logic to
ProductService.
Orchestrates product registration, update, and deletion. Coordinates with repositories for persistence.
Fetches the HTML of a product page and extracts the current price using CSS selectors or XPath. Handles retries and HTTP errors gracefully.
A @Scheduled component that runs at a configured interval and publishes a job message to RabbitMQ for every active
tracked product.
A RabbitMQ message listener (@RabbitListener) that consumes price-check jobs. Calls the scraper, persists the result,
and triggers email notifications when appropriate.
Sends HTML-formatted notification emails via JavaMailSender when a product's price falls at or below the user's target price.
[PriceUpdateScheduler]
β
β publish: { productId: 42 }
βΌ
[RabbitMQ Exchange: price.check.exchange]
β
β routing key: price.check
βΌ
[Queue: price.check.queue]
β
βΌ
[PriceCheckWorker] βββΊ scrape βββΊ save βββΊ notify (if needed)
Exchange Type: Direct
Queue: price.check.queue
Routing Key: price.check
| Column | Type | Description |
|---|---|---|
id |
BIGINT (PK) | Auto-generated ID |
name |
VARCHAR | Product display name |
url |
TEXT | Product page URL |
current_price |
DECIMAL | Last scraped price |
target_price |
DECIMAL | User-defined alert threshold |
notification_email |
VARCHAR | Recipient for alerts |
created_at |
TIMESTAMP | Registration time |
updated_at |
TIMESTAMP | Last update time |
| Column | Type | Description |
|---|---|---|
id |
BIGINT (PK) | Auto-generated ID |
product_id |
BIGINT (FK) | Reference to product |
price |
DECIMAL | Scraped price value |
checked_at |
TIMESTAMP | When price was recorded |
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch:
git checkout -b feature/your-feature-name - Commit your changes:
git commit -m "feat: add your feature" - Push to your branch:
git push origin feature/your-feature-name - Open a Pull Request
Please follow the existing code style and write unit tests for new functionality.
This project is licensed under the MIT License. See the LICENSE file for details.
Made with β and Spring Boot by meminksr
