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 :)
Top comments (0)