I successfully implemented a real-time chat application using Spring Boot, WebSocket, SockJS, and Stomp. My setup works seamlessly with both Angular and React Native Mobile App. I want to share the configuration and code to help others who might encounter similar requirements. Here’s my working setup:
Backend Configuration: Dependencies:
implementation 'org.springframework.boot:spring-boot-starter-websocket'
implementation 'org.webjars:sockjs-client:1.0.2'
implementation 'org.webjars:stomp-websocket:2.3.3'
WebSocket Configuration:
@Configuration
@EnableWebSocketMessageBroker
@Order(Ordered.HIGHEST_PRECEDENCE + 99)
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws")
.setAllowedOrigins("http://192.168.0.184:4201","http://localhost:4200")
.withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry brokerRegistry) {
brokerRegistry.setApplicationDestinationPrefixes("/app");
brokerRegistry.enableSimpleBroker("/topic", "/queue", "/user");
brokerRegistry.setUserDestinationPrefix("/user");
}
}
Controller for Handling Messages:
@RequiredArgsConstructor
@RestController
public class RealTimeChat {
private final SimpMessagingTemplate messagingTemplate;
private final ServiceMessage serviceMessage;
private final WebSocketSessionRegistry sessionRegistry;
private static final Logger LOGGER = LoggerFactory.getLogger(RealTimeChat.class);
@MessageMapping("/chat.sendPrivate")
public void sendPrivateMessage(@Payload DtoMessage chatMessage) {
try {
String recipientUser = chatMessage.getUser() + "";
LOGGER.info("Sending message to: {}", recipientUser);
messagingTemplate.convertAndSendToUser(recipientUser, "/queue/private", chatMessage);
} catch (Exception e) {
e.printStackTrace();
}
}
@MessageMapping("/chat.register")
public DtoMessage register(@Payload DtoMessage chatMessage, SimpMessageHeaderAccessor headerAccessor) {
try {
String sessionId = headerAccessor.getSessionId();
headerAccessor.getSessionAttributes().put(chatMessage.getUser() + "", sessionId);
sessionRegistry.registerSession(chatMessage.getUser(), sessionId);
return chatMessage;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
}
Frontend Configuration: React Hook for Connecting to WebSocket:
useEffect(() => {
const socket = new SockJS(wsUrl);
const client = new Client({
webSocketFactory: () => socket,
debug: str => console.warn(str),
onConnect: () => {
console.log('Connected to WebSocket');
client.subscribe('/chat.register', message => {
const receivedMessage = JSON.parse(message.body);
console.log('[WebSocket] Received message:', receivedMessage);
});
},
onDisconnect: () => {
console.log('Disconnected from WebSocket');
},
onStompError: error => {
console.error('Stomp error:', error);
},
});
client.activate();
setStompClient(client);
return () => {
client.deactivate();
};
}, [userId, tenantId]);
Sending a Private Message:
const sendMessage = () => {
const message = {
userIdSend: 'ed477317-5fe8-4841-a13d-e45e01eb94be',
userIdTo: '1',
content: 'Testing message...',
type: true,
};
stompClient.publish({
destination: '/app/chat.sendPrivate',
body: JSON.stringify(message),
});
setNewMessage('');
};