DEV Community

Amalia Hajarani
Amalia Hajarani

Posted on

How to: Integrating Socket.IO with Spring Boot and React (Spring Boot Implementation)

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

  1. Java version of 17.
  2. Visual Studio Code extension for Spring Initializr.

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
  3. We're going to use Spring Boot Version of 3.2.0. Image description
  4. We choose Java as the language. Image description
  5. Type your group id. Mine is com.tujuhsembilan
    Image description

  6. Type your artifact id. Mine is socketio.
    Image description

  7. Choose Jar as packaging type.
    Image description

  8. Choose Java Version of 17.
    Image description

  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
Enter fullscreen mode Exit fullscreen mode

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;

}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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";

}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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);
        };
    }

}
Enter fullscreen mode Exit fullscreen mode

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));
    }

}
Enter fullscreen mode Exit fullscreen mode

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

Top comments (2)

Collapse
 
emiemi671 profile image
emi-emi671

Thanks for sharing this. Do you know how to connect socket.io over https?
I am having some issues while running the socket.io over HTTPS as getting Cross-Origin Request Blocked. Here is StackOverflow

Collapse
 
luowenhua profile image
LuoWenHua

Good , I like