How to: Integrating Socket.IO with Spring Boot and React (Spring Boot Implementation)
Tags: socketio, springboot, beginners, tutorial
Published: 2023-12-06 02:29:52

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](https://medium.com/folksdev/spring-boot-netty-socket-io-example-3f21fcc1147d). You can also find the working github [here](https://github.com/gurkanucar/socketio-simple-chat/tree/master).

And my final result from following the tutorial can be seen at my github [repository](https://github.com/amalja0/simple-chat-socket-io). In this post I would like to focus on how to build the server side service so let's start cooking!

## Prerequisite
1. Java version of 17.
2. Visual Studio Code extension for [Spring Initializr](https://code.visualstudio.com/docs/java/java-spring-boot#:~:text=The%20Spring%20Initializr%20extension%20allows,for%20vscode%2Dspring%2Dinitializr%20.).

## Initializing Java Spring Boor project with Maven
1. Open your Visual Studio Code.
2. To create new project, usually I hit `ctrl` + `shift` + `p` altogether, choose `Spring Initializr: Create Maven Project`.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jsf1hdoz0acm1fjis1sq.png)
3. We're going to use Spring Boot Version of `3.2.0`.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/emcimuzfv941zsqrd0qt.png)
4. We choose `Java` as the language.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/923mrcdai8q5wf3a0c3k.png)
5. Type your group id. Mine is `com.tujuhsembilan`
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7xqkjddhz2bumpmuui45.png)

6. Type your artifact id. Mine is `socketio`.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t9c8h4l1h1jo4nwllmmv.png)
7. Choose `Jar` as packaging type.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4b7vrznsfu58f4aozgzo.png)
8. Choose Java Version of 17.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q9mckzv7277ylya8atpt.png)
9. 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
1. 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
1. 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:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qtmdo7a895egvcf7ttpw.png)