Building a Real-Time Messaging App with Spring Boot Kafka, Websocket, MongoDB and React

D2Y MVN
5 min readOct 17, 2024

In this tutorial, we will create a simple real-time messaging application using Spring Boot for the backend and React for the frontend. The application will use WebSockets to enable real-time communication and Kafka for message queuing.

Prerequisites

Before we begin, make sure you have the following installed:

  • Java 11 or higher: for running Spring Boot
  • Maven: for managing Java dependencies
  • Node.js and npm: for running the React application
  • Apache Kafka: for message queuing
  • MongoDB: for persistent data

Backend Setup

Step 1: Initialize the Spring Boot Project

  1. Create a new Spring Boot project using Spring Initializr (https://start.spring.io/).
  2. Select the following dependencies:
  • Spring Web
  • Spring WebSocket
  • Spring Data MongoDB
  • Spring for Apache Kafka
  • Lombok

Step 2: Add Dependencies

Add the following dependencies in your pom.xml file:

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>

Step 3: Configure WebSocket and Kafka Producer

Create a new class called WebSocketConfig:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

@Autowired
private KafkaTemplate<String, String> kafkaTemplate;

@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new WebSocketMessageHandler(kafkaTemplate), "/ws").setAllowedOrigins("*");
}
}

Next, create new file KafkaProducerService:

import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;

@Service
public class KafkaProducerService {

private final KafkaTemplate<String, String> kafkaTemplate;

public KafkaProducerService(KafkaTemplate<String, String> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}

public void sendMessage(String topic, String message) {
kafkaTemplate.send(topic, message);
}
}

Step 4: Create Message Entity and Repository

Create a new Message class:

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Document(collection = "messages")
public class Message {
@Id
private String id;
private String content;
}

Next, create a MessageRepository interface:

import org.springframework.data.mongodb.repository.MongoRepository;

public interface MessageRepository extends MongoRepository<Message, String> {
}

Step 5: Implement Message Service

Create a MessageService class to handle message operations:

import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

@Service
public class MessageService {
private final MessageRepository messageRepository;

public MessageService(MessageRepository messageRepository) {
this.messageRepository = messageRepository;
}

public Message saveMessage(String content) {
Message message = new Message();
message.setContent(content);
return messageRepository.save(message);
}
}

Step 6: Create WebSocket Message Handler

Implement the WebSocketMessageHandler:

import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import org.springframework.kafka.core.KafkaTemplate;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class WebSocketMessageHandler extends TextWebSocketHandler {

private final KafkaTemplate<String, String> kafkaTemplate;
private static final List<WebSocketSession> webSocketSessions = new CopyOnWriteArrayList<>();

public WebSocketMessageHandler(KafkaTemplate<String, String> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}

@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
webSocketSessions.add(session);
}

@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
kafkaTemplate.send("message-topic", message.getPayload());
}

@Override
public void afterConnectionClosed(WebSocketSession session, org.springframework.web.socket.CloseStatus status) throws Exception {
webSocketSessions.remove(session);
}

public static List<WebSocketSession> getWebSocketSessions() {
return webSocketSessions;
}
}

Step 7: Configure CORS

Create a WebConfig class to configure CORS settings:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:5173")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true);
}
}

Step 8: Create Kafka Consumer Service

Implement the KafkaConsumerService:

import com.d2y.messaging_app.services.MessageService;
import com.d2y.messaging_app.configs.WebSocketMessageHandler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

import java.io.IOException;

@Service
@RequiredArgsConstructor
@Slf4j
public class KafkaConsumerService {

private final MessageService messageService;

@KafkaListener(topics = "message-topic", groupId = "messaging")
public void listen(String message) {
for (WebSocketSession session : WebSocketMessageHandler.getWebSocketSessions()) {
try {
session.sendMessage(new TextMessage(message));
messageService.saveMessage(message);
} catch (IOException e) {
log.error("Error sending message to WebSocket session: {}", e.getMessage());
}
}
}
}

Step 9: Run the Backend

Run your Spring Boot application using your IDE or by executing the command:

mvn spring-boot:run

Frontend Setup

Step 1: Create a React Application

Create a new React application using Create React App:

npm create vite@latest messaging-app
cd messaging-app

Step 2: Implement WebSocket Logic

In src folder, create a new component named WebSocketComponent.js:

import { useState, useEffect } from "react";

const WebSocketComponent = () => {
const [message, setMessage] = useState("");
const [messages, setMessages] = useState([]);
const [socket, setSocket] = useState(null);

useEffect(() => {
const ws = new WebSocket("ws://localhost:5000/ws");
setSocket(ws);

ws.onmessage = (event) => {
const parsedMessage = JSON.parse(event.data);
setMessages((prevMessages) => [...prevMessages, parsedMessage]);
};

return () => {
ws.close();
};
}, []);

const sendMessage = () => {
if (message.trim() !== "" && socket) {
const messageObject = { content: message };
socket.send(JSON.stringify(messageObject));
setMessage("");
}
};

const handleKeyDown = (event) => {
if (event.key === "Enter") {
event.preventDefault();
sendMessage();
}
};

return (
<div className="flex flex-col h-screen bg-gray-100">
<h1 className="text-2xl font-bold bg-blue-800 text-gray-200 py-4 px-8 mb-4">
Messaging App
</h1>
<div className="flex-grow overflow-y-auto p-4">
<div className="space-y-2">
{messages.map((msg, index) => (
<div key={index} className="bg-white p-2 rounded shadow-sm">
{msg.content}
</div>
))}
</div>
</div>

<div className="p-4 bg-white border-t">
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
onKeyDown={handleKeyDown}
className="w-full p-2 border rounded"
placeholder="Type your message..."
/>
<button onClick={sendMessage} className="mt-2 bg-blue-600 text-white py-2 px-4 rounded">
Send
</button>
</div>
</div>
);
};

export default WebSocketComponent;

Step 3: Create the Messaging UI

Modify the src/App.js file to include the WebSocket component:

import React from "react";
import WebSocketComponent from "./WebSocketComponent";

const App = () => {
return (
<div>
<WebSocketComponent />
</div>
);
};

export default App;

Step 4: Run the Frontend

Run your React application:

npm run dev

The frontend should be available at http://localhost:5173.

Congratulations! You have built a simple real-time messaging application using Spring Boot for the backend and React for the frontend. You can extend this application by adding features like user authentication, message history, and more.

Further Enhancements

  • User Authentication: Implement JWT authentication for user management.
  • Message Persistence: Enhance the backend to persist messages in a database.
  • Chat Rooms: Allow users to join different chat rooms.
  • Notifications: Add notifications for new messages.

Feel free to customize and improve this application to suit your needs!

Repositories

These repositories contain the complete source code for both the backend and frontend. Clone them to explore further, contribute, or build upon this project. Happy coding!

--

--

D2Y MVN
D2Y MVN

Written by D2Y MVN

Lets make a plane and take a risk!

No responses yet