How to: Integrating Socket.IO with Spring Boot and React (Spring Boot Implementation)
Amalia Hajarani
Posted on December 6, 2023
Hallo, dear reader. This post is actually my attempt to following an existing tutorial that was made by Gürkan UÇAR which tutorial can be seen at here. You can also find the working github here.
And my final result from following the tutorial can be seen at my github repository. In this post I would like to focus on how to build the server side service so let's start cooking!
Prerequisite
- Java version of 17.
- Visual Studio Code extension for Spring Initializr.
Initializing Java Spring Boor project with Maven
- Open your Visual Studio Code.
- To create new project, usually I hit
ctrl
+shift
+p
altogether, chooseSpring Initializr: Create Maven Project
. - We're going to use Spring Boot Version of
3.2.0
. - We choose
Java
as the language. -
For the dependencies, you can seet at my POM.xml. If you can't find it, that's fine because you can add it manually later, once your project is generated.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.0</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.tujuhsembilan</groupId> <artifactId>socketio</artifactId> <version>0.0.1-SNAPSHOT</version> <name>socketio</name> <description>Demo project for Spring Boot</description> <properties> <java.version>17</java.version> <netty-socketio.version>1.7.17</netty-socketio.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.corundumstudio.socketio</groupId> <artifactId>netty-socketio</artifactId> <version>${netty-socketio.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-rest</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
10. Choose where you want to save the project.
## Creating project skeleton
1. Open `src\main\java\com\{your_group_id}\{your_artifact_id}`. Mine is `src\main\java\com\tujuhsembilan\socketio`.
2. Now I would suggest you to create few new packages like this:
```
src/
└── main/
└── java/
└── com/
└── tujuhsembilan/
└── socketio/
├── configuration/
├── constants/
├── controller/
├── enums/
├── model/
├── repository/
├── service/
└── socket/
Configuring application.properties
Usually, you can find application.properties
at src\main\resources\application.properties
. You can copy paste these lines to application.properties
. Don't forget to make sure that the port for socket is free to use and more importantly, change the host to your IPv4 address.
socket-server.port=8086 // you can change this to port that is free to use
socket-server.host=191.167.1.28 // change this to your IPv4 address
spring.h2.console.enabled=true
spring.datasource.url=jdbc:h2:mem:database
Creating configurations
-
Open configuration package. Create a new class called
SocketIOConfig.java
. This is the content of the class:package com.tujuhsembilan.socketio.configuration; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.corundumstudio.socketio.SocketIOServer; @Configuration public class SocketIOConfig { @Value("${socket-server.host}") private String host; @Value("${socket-server.port}") private Integer port; @Bean public SocketIOServer socketIOServer() { com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration(); config.setHostname(host); config.setPort(port); return new SocketIOServer(config); } }
2. Still in the same package, create another new class called `ServerCommandLineRunner.java`. This is the content:
```
package com.tujuhsembilan.socketio.configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import com.corundumstudio.socketio.SocketIOServer;
import lombok.RequiredArgsConstructor;
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ServerCommandLineRunner implements CommandLineRunner {
private final SocketIOServer server;
@Override
public void run(String... args) throws Exception {
server.start();
}
}
Creating model
We only need a model called Message
. Open your model
package. Create a new file called Message.java
. This is the content:
package com.tujuhsembilan.socketio.model;
import java.time.LocalDateTime;
import java.util.UUID;
import com.tujuhsembilan.socketio.enums.MessageType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Builder
@Getter
@Setter
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Message {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@Column
@Enumerated(EnumType.STRING)
private MessageType messageType;
@Column
private String room;
@Column
private String username;
@Column
private String message;
@Column
private LocalDateTime createdAt;
}
Creating enums
As you might notice, we not yet have enums for messageType
in the model. Hence, we need to create one. Open enums
package, create a new class called MessageType.java
and copy paste this:
package com.tujuhsembilan.socketio.enums;
public enum MessageType {
SERVER, CLIENT
}
Now the Message
model should be working fine.
Creating constants
Now, before moving further, let's create some constants. Open constants
package. Create a new file called Constants.js
and below is the content:
package com.tujuhsembilan.socketio.constants;
public class Constants {
public static final String WELCOME_MESSAGE = "%s joined to chat";
public static final String DISCONNECT_MESSAGE = "%s disconnected";
}
Creating repository
In repository package, create a new repository class called MessageRepository.java
. This is the content for it:
package com.tujuhsembilan.socketio.repository;
import java.util.List;
import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.tujuhsembilan.socketio.model.Message;
@Repository
public interface MessageRepository extends JpaRepository<Message, UUID> {
List<Message> findAllByRoom(String room);
}
Creating services
-
In service package, create a new class called
MessageService.java
.package com.tujuhsembilan.socketio.service; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.tujuhsembilan.socketio.model.Message; import com.tujuhsembilan.socketio.repository.MessageRepository; import lombok.RequiredArgsConstructor; @Service @RequiredArgsConstructor(onConstructor = @__(@Autowired)) public class MessageService { private final MessageRepository messageRepository; public List<Message> getMessage(String room) { return messageRepository.findAllByRoom(room); } public Message saveMessage(Message message) { return messageRepository.save(message); } }
2. Create another class in the same package called SocketService.java
.
```
package com.tujuhsembilan.socketio.service;
import org.springframework.stereotype.Service;
import com.corundumstudio.socketio.SocketIOClient;
import com.tujuhsembilan.socketio.enums.MessageType;
import com.tujuhsembilan.socketio.model.Message;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class SocketService {
private final MessageService messageService;
public void sendSocketmessage(SocketIOClient senderClient, Message message, String room) {
for (
SocketIOClient client: senderClient.getNamespace().getRoomOperations(room).getClients()
) {
if (!client.getSessionId().equals(senderClient.getSessionId())) {
client.sendEvent("read_message", message);
}
}
}
public void saveMessage(SocketIOClient senderClient, Message message) {
Message storedMessage = messageService.saveMessage(
Message.builder()
.messageType(MessageType.CLIENT)
.message(message.getMessage())
.room(message.getRoom())
.username(message.getUsername())
.build()
);
sendSocketmessage(senderClient, storedMessage, message.getRoom());
}
public void saveInfoMessage(SocketIOClient senderClient, String message, String room) {
Message storedMessage = messageService.saveMessage(
Message.builder()
.messageType(MessageType.SERVER)
.message(message)
.room(room)
.build()
);
sendSocketmessage(senderClient, storedMessage, room);
}
}
Creating socket module
Now, we are going to create a socket module. Open socket
package, create a new file called SocketModule.java
like this:
package com.tujuhsembilan.socketio.socket;
import java.util.stream.Collectors;
import org.springframework.stereotype.Component;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.listener.ConnectListener;
import com.corundumstudio.socketio.listener.DataListener;
import com.corundumstudio.socketio.listener.DisconnectListener;
import com.tujuhsembilan.socketio.constants.Constants;
import com.tujuhsembilan.socketio.model.Message;
import com.tujuhsembilan.socketio.service.SocketService;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
public class SocketModule {
private final SocketIOServer server;
private final SocketService socketService;
public SocketModule(SocketIOServer server, SocketService socketService) {
this.server = server;
this.socketService = socketService;
server.addConnectListener(this.onConnected());
server.addDisconnectListener(this.onDisconnected());
server.addEventListener("send_message", Message.class, this.onChatReceived());
}
private DataListener<Message> onChatReceived() {
return (senderClient, data, ackSender) -> {
log.info(data.toString());
socketService.saveMessage(senderClient, data);
};
}
private ConnectListener onConnected() {
return (client) -> {
var params = client.getHandshakeData().getUrlParams();
String room = params.get("room").stream().collect(Collectors.joining());
String username = params.get("username").stream().collect(Collectors.joining());
client.joinRoom(room);
socketService.saveInfoMessage(client, String.format(Constants.WELCOME_MESSAGE, username), room);
log.info("Socket ID[{}] - room[{}] - username [{}] Connected to chat module through", client.getSessionId().toString(), room, username);
};
}
private DisconnectListener onDisconnected() {
return client -> {
var params = client.getHandshakeData().getUrlParams();
String room = params.get("room").stream().collect(Collectors.joining());
String username = params.get("username").stream().collect(Collectors.joining());
socketService.saveInfoMessage(client, String.format(Constants.DISCONNECT_MESSAGE, username), room);
log.info("Socket ID[{}] - room[{}] - username [{}] discnnected to chat module through", client.getSessionId().toString(), room, username);
};
}
}
Creating controller
Now, to get the message in a certain room, let's create a controller. Open controller
package, create a new file called MessageController.java
. This is the content for our controller:
package com.tujuhsembilan.socketio.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.tujuhsembilan.socketio.model.Message;
import com.tujuhsembilan.socketio.service.MessageService;
import lombok.RequiredArgsConstructor;
@RestController
@RequestMapping("/message")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class MessageController {
private final MessageService messageService;
@CrossOrigin
@GetMapping("/{room}")
public ResponseEntity<List<Message>> getMessages(@PathVariable String room) {
return ResponseEntity.ok(messageService.getMessage(room));
}
}
Running the application
Since I have extension for Spring Boot in my Visual Studio code, I just went into SocketioApplication.java which located in src\main\java\com\tujuhsembilan\socketio\SocketioApplication.java
and click Run
. If the applicaion running well, this is the kind of log that you will see:
Posted on December 6, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
December 6, 2023