DEV Community

Yoan Sredkov
Yoan Sredkov

Posted on • Edited on

Java Chat App - Backend

Hello fellow devs,

In this series of posts, I'll demonstrate the power of Java + Spring Boot + Vue.js. I'll show you how to create a basic chat server and frontend. There will be no "user" entity, but rather a user-less "login". Lets go!

First, go to https://start.spring.io/.
This is the spring initializer, which helps you to generate your app + the respective dependencies in a single .zip file, which you will later download.

Those are the dependencies I've selected:

Alt Text

So, now that we have the project initialized, let's go write some code! :)

We'll start right away, by defining our "message" class.
This is mine:

// ... imports here ... @Data public class ChatMessage{ private String content; private String senderUsername; private String recipientUsername; private Timestamp createdAt; // JSON toString method, we will need this later @Override public String toString() { return "{\"ChatMessage\":{" + " \"content\":\"" + content + "\"" + ", \"senderUsername\":\"" + senderUsername + "\"" + ", \"recipientUsername\":\"" + recipientUsername + "\"" + ", \"createdAt\":" + createdAt + "}}"; } } 
Enter fullscreen mode Exit fullscreen mode

The @data annotation is an "all-together" annotation, which contains @RequiredArgsConstructor, @Getter, @setter, @EqualsAndHashCode, @ToString in one. Isn't that beautiful?

This is our base class, and as you can see, it's 100% simple. Let's go on to the WS (websocket) config, as this is the other core of our app.
I've created the basic class WebSocketConfiguration as follows:

// imports... @EnableWebSocketMessageBroker @Configuration public class WebsocketConfiguration implements WebSocketMessageBrokerConfigurer { // registers basic endpoints @Override public void registerStompEndpoints(final StompEndpointRegistry registry) { registry.addEndpoint("/chat-app", "/sockjs-node", "/ws", "/secured/room").setAllowedOriginPatterns("*").withSockJS(); // Please note the .setAllowedOriginPatterns, as it enables cross-origin requests } // configures message broker @Override public void configureMessageBroker(final MessageBrokerRegistry registry) { registry.enableSimpleBroker("/topic"); // enables basic message broker over the /topic endpoint registry.setApplicationDestinationPrefixes("/chat-app"); // enables /chat-app as a destination registry.setUserDestinationPrefix("/secured/user"); // used so i'll be able to send messages to specific users } 
Enter fullscreen mode Exit fullscreen mode

Yes, It really is that easy. We'll follow up with a Golang chat application in another post, so you can see the difference.

We'll continue by defining our controller. This is my chat controller:

// imports here... @Component @Controller public class ChatController { private final SimpMessagingTemplate messagingTemplate; // constructor public ChatController(final SimpMessagingTemplate messagingTemplate) { this.messagingTemplate = messagingTemplate; this.messagingTemplate.setMessageConverter(new SimpleMessageConverter()); // converts messages to bytes } // Receives and emits chat messages to secured user rooms @MessageMapping("/message/{room}") // receives from specific "room" @SendTo("/topic/messages/{room}") // sends to specific queue or "room" public ChatMessage sendChatMessage(final ChatMessage message) { messagingTemplate.convertAndSend("/topic/messages/" + message.getRecipientUsername(), message.toString().getBytes()); return message; } } 
Enter fullscreen mode Exit fullscreen mode

As you can see, the code is quite straightforward and simple.
The endpoint receives a ChatMessage object from a frontend app, and emits it back to a specific user. This is possible because of our /secured/user endpoint in our config. ( It's also good to note that the frontend app is subscribed to a single topic/endpoint - /topic/messages + {username}.

We'll also add a CORS filter, as follows:

// imports here... @Component public class CorsFilter implements Filter { @Override public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException { final HttpServletRequest req = (HttpServletRequest) servletRequest; final HttpServletResponse res = (HttpServletResponse) servletResponse; final String origin = req.getHeader("Origin"); res.addHeader("Access-Control-Allow-Origin", origin); res.addHeader("Access-Control-Allow-Credentials", "true"); res.addHeader("Access-Control-Allow-Headers", "*"); res.addHeader("Access-Control-Allow-Methods", "*"); filterChain.doFilter(req, res); } } 
Enter fullscreen mode Exit fullscreen mode

This filter will eliminate all the CORS-related issues we would have had if we started trying to connect to the server right away.

We'll continue with the frontend application in the next post. We will create a basic chat client, and enhance our backend so it will remember the IP addresses of the connected users, so they won't have to enter a new username every time.

We'll build this:
Alt Text

Have a nice day, and enjoy programming!
See you again :)

Top comments (0)