A reactive Q&A backend built with Spring Boot 3, WebFlux, MongoDB, Apache Kafka, and Elasticsearch. BrainThread lets users post questions, write answers, leave likes, and perform blazing-fast full-text search. The core database runs on MongoDB, full-text search is powered by Elasticsearch, and view counts are tracked asynchronously via a Kafka event pipeline.
Client (HTTP)
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโ
โ QuestionController โ โ REST layer (Spring WebFlux)
โโโโโโโโโโฌโโโโโโโโโโโโโ
โ IQuestionService (interface)
โผ
โโโโโโโโโโโโโโโโโโโโโโโ
โ QuestionService โ โ Business logic layer
โโโโโโโโโโฌโโโโโโโโโโโโโ
โ ReactiveMongoRepository
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ QuestionRepository โ โ Reactive MongoDB queries
โโโโโโโโโโฌโโโโโโโโโโโโโโโโโโ
โ
โผ
MongoDB (BrainThread db)
โโ View Count Event Flow (Async / Kafka) โโโโโโโโโโโโโโโโโโโโโโ
GET /api/questions/{id}
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโ
โ QuestionService โ โ fetches question, then fires event
โโโโโโโโโโฌโโโโโโโโโโโโโ
โ publishes ViewCountEvent
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ KafkaEventProducer โ โ sends to "view-count-topic" (keyed by targetId)
โโโโโโโโโโฌโโโโโโโโโโโโโโโโโโ
โ
โผ
Apache Kafka (view-count-topic)
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ KafkaEventConsumer โ โ @KafkaListener (3 concurrent threads)
โโโโโโโโโโฌโโโโโโโโโโโโโโโโโโ
โ increments viewCount via repository
โผ
MongoDB (BrainThread db)
POST /api/questions
โ Controller receives QuestionRequestDTO (validated)
โ Service builds Question model, saves via repository (reactive Mono)
โ QuestionAdapter maps Question โ QuestionResponseDTO
โ Response returned as Mono<QuestionResponseDTO>
GET /api/questions/author/{authorId}
โ Controller delegates to service
โ Repository streams results (reactive Flux)
โ QuestionAdapter maps each Question โ QuestionResponseDTO
โ Response returned as Flux<QuestionResponseDTO>
GET /api/questions?cursor=...&limit=10
โ Service decodes base64 timestamp cursor
โ Repository fetches top N records older than cursor
โ Streamed as Flux<QuestionResponseDTO>
GET /api/questions/search?query=...&page=0&size=10
โ Service builds PageRequest from page/size params
โ Repository runs MongoDB $or regex query (title or content)
โ Streamed as Flux<QuestionResponseDTO>
GET /api/questions/tag/{tag}
โ Repository queries documents where tags array contains the value
โ Streamed as Flux<QuestionResponseDTO>
GET /api/questions/{id}
โ Repository fetches question by ID (Mono)
โ QuestionAdapter maps Question โ QuestionResponseDTO
โ doOnSuccess fires a ViewCountEvent โ KafkaEventProducer sends to Kafka
โ KafkaEventConsumer receives event, increments viewCount in MongoDB (async)
| Pattern / Concept | Where | Why |
|---|---|---|
| Adapter Pattern | QuestionAdapter |
Converts Question (DB model) โ QuestionResponseDTO (API shape). Decouples the database model from what the client sees. |
| Interface Segregation (SOLID) | IQuestionService |
Controller depends on an interface, not the concrete QuestionService. Makes the service swappable and independently testable. |
| DTO Pattern | QuestionRequestDTO / QuestionResponseDTO |
Separates the API contract from the internal model. The response DTO includes id; the request DTO never exposes it. |
| Repository Pattern | QuestionRepository |
Abstracts all database access behind a single interface. The service never writes raw MongoDB queries. |
| Reactive Programming (Project Reactor) | Entire stack | Uses Mono<T> (single result) and Flux<T> (stream of results) throughout. Zero threads are blocked waiting for I/O. |
| Builder Pattern | Question, QuestionRequestDTO, QuestionResponseDTO |
Lombok @Builder makes object construction readable and avoids telescoping constructors. |
| Dependency Injection | @RequiredArgsConstructor (Lombok) |
Final fields are injected by Spring at startup. No @Autowired field injection needed. |
| Bean Validation | @NotBlank, @Size on DTOs |
Jakarta Validation enforces input rules at the request layer, before any business logic runs. |
| Pagination | PageRequest / Pageable |
searchQuestions and getQuestionByTag accept page and size params, passed as a Pageable to the repository. |
| Audit Timestamps | @CreatedDate, @LastModifiedDate on Question |
Spring Data auto-populates createdAt / updatedAt for MongoDB documents. |
| Event-Driven Architecture | ViewCountEvent, KafkaEventProducer, KafkaEventConsumer |
View count increments are decoupled from the read path โ the HTTP response is returned immediately and the counter update happens asynchronously via Kafka. |
| Observer / Pub-Sub Pattern | Kafka topic view-count-topic |
Producer publishes events; consumer subscribes independently. Neither knows about the other, keeping them fully decoupled. |
| Technology | Version |
|---|---|
| Java | 17 |
| Spring Boot | 3.4.2 |
| Spring WebFlux | via Boot starter |
| Spring Data MongoDB Reactive | via Boot starter |
| Spring Data Elasticsearch | via Boot starter |
| Project Reactor | via WebFlux |
| Apache Kafka | 3.x |
| Spring Kafka | via Boot starter |
| Lombok | Latest |
| Jakarta Bean Validation | via Boot starter |
| Gradle | 9.x |
| MongoDB | 6+ recommended |
| Elasticsearch | 8.x recommended |
- Java 17+ โ Download
- MongoDB running on port
27017โ local install or Docker (see below) - Apache Kafka running on port
9092โ local install or Docker (see below) - Elasticsearch running on port
9200โ local install or Docker (see below) - Git โ Download
Spring will create the
BrainThreaddatabase automatically on the first write. You do not need to create it manually.
git clone https://github.com/<your-username>/BrainThread.git
cd BrainThreadOption A โ Docker (recommended if you don't have Mongo installed):
docker run -d -p 27017:27017 --name mongo mongo:latestOption B โ Local install (macOS/Linux):
mongod --dbpath /data/dbOption B โ Local install (Windows):
mongod --dbpath "C:\data\db"Docker (easiest โ runs both Zookeeper and Kafka):
docker run -d --name zookeeper -p 2181:2181 zookeeper:latest
docker run -d --name kafka -p 9092:9092 \
-e KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 \
-e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092 \
--link zookeeper confluentinc/cp-kafka:latestThe topic
view-count-topicis created automatically by the application on first publish.
Docker (easiest):
docker run -d --name elasticsearch -p 9200:9200 -e "discovery.type=single-node" -e "xpack.security.enabled=false" docker.elastic.co/elasticsearch/elasticsearch:8.12.0# macOS / Linux
./gradlew bootRun
# Windows
.\gradlew.bat bootRunThe app starts on http://localhost:8080.
src/main/resources/application.properties:
spring.application.name=BrainThread
spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017
spring.data.mongodb.database=BrainThread
spring.data.mongodb.auto-index-creation=true
# Elasticsearch
elasticsearch.uris=http://localhost:9200
# Kafka
spring.kafka.bootstrap-servers=localhost:9092
spring.kafka.consumer.group-id=view-count-consumerBase URLs:
- Questions:
http://localhost:8080/api/questions - Answers:
http://localhost:8080/api/answers - Likes:
http://localhost:8080/api/likes
Request Body:
{
"title": "What is reactive programming?",
"content": "I want to understand the core concepts of reactive programming and how it differs from traditional threading models.",
"userId": "user_abc123"
}Validation:
| Field | Rules |
|---|---|
title |
Required, 10โ100 characters |
content |
Required, 10โ1000 characters |
userId |
Required, non-blank |
Response 200 OK:
{
"id": "65f1a2b3c4d5e6f7a8b9c0d1",
"title": "What is reactive programming?",
"content": "I want to understand the core concepts of reactive programming and how it differs from traditional threading models.",
"userId": "user_abc123",
"createdAt": "2026-03-10T16:58:00.000+00:00",
"updatedAt": "2026-03-10T16:58:00.000+00:00"
}cURL:
curl -X POST http://localhost:8080/api/questions \
-H "Content-Type: application/json" \
-d '{
"title": "What is reactive programming?",
"content": "I want to understand the core concepts of reactive programming and how it differs from traditional threading models.",
"userId": "user_abc123"
}'PowerShell:
Invoke-RestMethod -Method POST -Uri "http://localhost:8080/api/questions" `
-ContentType "application/json" `
-Body '{
"title": "What is reactive programming?",
"content": "I want to understand the core concepts of reactive programming and how it differs from traditional threading models.",
"userId": "user_abc123"
}'Returns all questions posted by a user as a streaming JSON array.
Path Variable: authorId โ the userId string used when creating the question.
Response 200 OK:
[
{
"id": "65f1a2b3c4d5e6f7a8b9c0d1",
"title": "What is reactive programming?",
"content": "...",
"userId": "user_abc123",
"createdAt": "2026-03-10T16:58:00.000+00:00",
"updatedAt": "2026-03-10T16:58:00.000+00:00"
}
]cURL:
curl http://localhost:8080/api/questions/author/user_abc123PowerShell:
Invoke-RestMethod -Uri "http://localhost:8080/api/questions/author/user_abc123"Returns questions globally, paginated using a cursor (timestamp) for maximum database performance.
Query Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
cursor |
String |
null |
The createdAt timestamp of the last item you saw. If omitted, returns the absolute newest questions. |
limit |
int |
10 |
Maximum results to return |
Response 200 OK:
[
{
"id": "65f1a2b3c4d5e6f7a8b9c0d1",
"title": "What is reactive programming?",
"content": "...",
"userId": "user_abc123",
"createdAt": "2026-03-10T16:58:00.000",
"updatedAt": "2026-03-10T16:58:00.000"
}
]cURL:
# Initial load (no cursor)
curl "http://localhost:8080/api/questions?limit=5"
# Next page (using the timestamp of the last item in the previous request)
curl "http://localhost:8080/api/questions?cursor=2026-03-10T16:58:00.000&limit=5"PowerShell:
Invoke-RestMethod -Uri "http://localhost:8080/api/questions?cursor=2026-03-10T16:58:00.000&limit=5"Searches across title and content using a case-insensitive regex. Supports pagination.
Query Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
query |
String |
required | Search term |
page |
int |
0 |
Page number (0-indexed) |
size |
int |
10 |
Results per page |
Response 200 OK:
[
{
"id": "65f1a2b3c4d5e6f7a8b9c0d1",
"title": "What is reactive programming?",
"content": "...",
"userId": "user_abc123",
"createdAt": "...",
"updatedAt": "..."
}
]cURL:
# Basic search
curl "http://localhost:8080/api/questions/search?query=reactive"
# With pagination
curl "http://localhost:8080/api/questions/search?query=reactive&page=0&size=5"PowerShell:
Invoke-RestMethod -Uri "http://localhost:8080/api/questions/search?query=reactive&page=0&size=5"Returns all questions that have the given tag. Supports pagination.
Path Variable: tag โ the tag value to filter by (must be stored in the tags array on the document).
Query Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
page |
int |
0 |
Page number (0-indexed) |
size |
int |
10 |
Results per page |
Response 200 OK:
[
{
"id": "65f1a2b3c4d5e6f7a8b9c0d1",
"title": "What is reactive programming?",
"content": "...",
"userId": "user_abc123",
"createdAt": "...",
"updatedAt": "..."
}
]cURL:
# Basic tag filter
curl "http://localhost:8080/api/questions/tag/java"
# With pagination
curl "http://localhost:8080/api/questions/tag/java?page=0&size=5"PowerShell:
Invoke-RestMethod -Uri "http://localhost:8080/api/questions/tag/java?page=0&size=5"Fetches a single question by its MongoDB ID. Asynchronously fires a view count event to Kafka.
Path Variable: id โ the MongoDB document ID.
Response 200 OK:
{
"id": "65f1a2b3c4d5e6f7a8b9c0d1",
"title": "What is reactive programming?",
"content": "...",
"userId": "user_abc123",
"viewCount": 42,
"createdAt": "2026-03-10T16:58:00.000+00:00",
"updatedAt": "2026-03-10T16:58:00.000+00:00"
}cURL:
curl http://localhost:8080/api/questions/65f1a2b3c4d5e6f7a8b9c0d1PowerShell:
Invoke-RestMethod -Uri "http://localhost:8080/api/questions/65f1a2b3c4d5e6f7a8b9c0d1"โน๏ธ The
viewCountfield is incremented asynchronously after the response is returned โ the user never waits for it.
Powered by Elasticsearch, querying both the Title and Content fields for highly optimized search results.
Query Parameters:
query(String): Search term
cURL:
curl "http://localhost:8080/api/questions/elasticsearch?query=reactive"PowerShell:
Invoke-RestMethod -Uri "http://localhost:8080/api/questions/elasticsearch?query=reactive"Submit an answer replying to a specific question.
Request Body:
{
"content": "Reactive programming handles concurrency with an event loop, drastically reducing memory overhead.",
"questionId": "65f1a2b3c4d5e6f7a8b9c0d1",
"userId": "user_expert89"
}cURL:
curl -X POST http://localhost:8080/api/answers \
-H "Content-Type: application/json" \
-d '{
"content": "Reactive programming handles concurrency with an event loop, drastically reducing memory overhead.",
"questionId": "65f1a2b3c4d5e6f7a8b9c0d1",
"userId": "user_expert89"
}'Streams all the answers that belong to a single question.
cURL:
curl http://localhost:8080/api/answers/question/65f1a2b3c4d5e6f7a8b9c0d1Dynamically adds, flips, or removes a like depending on the user's current like state for the target entity (such as a Question or an Answer).
Query Parameters:
| Param | Type | Description |
|---|---|---|
targetId |
String |
MongoDB ID of the liked entity |
targetType |
String |
e.g. Question or Answer |
userId |
String |
ID of the user performing the like |
cURL:
curl -X POST "http://localhost:8080/api/likes/toggle?targetId=65f1a2b3c4d5e6f7a8b9c0d1&targetType=Question&userId=user_abc123"Quickly calculate the absolute number of affirmative likes applied to a target.
cURL:
curl "http://localhost:8080/api/likes/count/likes?targetId=65f1a2b3c4d5e6f7a8b9c0d1&targetType=Question"# macOS / Linux
./gradlew test
# Windows
.\gradlew.bat testTest reports are generated at build/reports/tests/test/index.html.
src/main/java/com/ishan/BrainThread/
โโโ BrainThreadApplication.java # Entry point (@SpringBootApplication)
โโโ controllers/
โ โโโ QuestionController.java # REST endpoints (Spring WebFlux)
โโโ service/
โ โโโ IQuestionService.java # Service interface
โ โโโ QuestionService.java # Business logic implementation
โโโ repositories/
โ โโโ QuestionRepository.java # ReactiveMongoRepository queries
โโโ models/
โ โโโ Question.java # MongoDB document model (@Document, includes viewCount)
โโโ dto/
โ โโโ QuestionRequestDTO.java # Inbound request shape (validated)
โ โโโ QuestionResponseDTO.java # Outbound response shape (includes id)
โโโ adapter/
โ โโโ QuestionAdapter.java # Question โ QuestionResponseDTO mapping
โโโ events/
โ โโโ ViewCountEvent.java # Kafka event payload (targetId, targetType, timestamp)
โโโ producer/
โ โโโ KafkaEventProducer.java # Publishes ViewCountEvent to Kafka topic
โโโ consumers/
โ โโโ KafkaEventConsumer.java # @KafkaListener โ increments viewCount in MongoDB
โโโ config/
โ โโโ KafkaConfig.java # ProducerFactory, ConsumerFactory, KafkaTemplate, ListenerContainerFactory
โโโ utils/
โโโ CursorUtils.java # Base64 encode/decode for cursor-based pagination
- Build and integrate a Feed Generation Service
- Implement a prefix-based search engine , ui , auth , recommendation eng
- Convert all remaining code into reactive (Mono/Flux) code
- Fork the repo
- Create your feature branch:
git checkout -b feature/my-feature - Commit your changes:
git commit -m 'Add my feature' - Push to the branch:
git push origin feature/my-feature - Open a Pull Request