Java Chat App - Backend
Yoan Sredkov
Posted on March 1, 2021
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:
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
+ "}}";
}
}
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
}
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;
}
}
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);
}
}
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.
Have a nice day, and enjoy programming!
See you again :)
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
November 30, 2024
November 30, 2024