Java Chat App - Backend

joan41868

Yoan Sredkov

Posted on March 1, 2021

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 :)

💖 💪 🙅 🚩
joan41868
Yoan Sredkov

Posted on March 1, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related