<p>long time i didn't had to write a post here. I guess i am really stuck...
I built long time ago a monolithic app based on react and express that was handling a chat with socket.io. I remember i did struggle a little but at the end i make it work.</p>
<p>I am now reconverting the same app into microservices with kubernetes (on GKE) and after build the chat backend and the front, i just cannot make the chat work. It's seems somehow the socket.io instances are not connected. I tryed a lot of different things and i am now asking help regarding it. I will share bellow the parts of the code that are implying it.</p>
<p><strong>CHAT BACKEND WITH EXPRESS:</strong></p>
<p>There i am declaring a middleware to pass io as req.io to be able to use in a specific endpoint. This part work fine (at least it's seems to me)</p>
<pre><code>require(&quot;express-async-errors&quot;)
const express = require(&quot;express&quot;)
const helmet = require(&quot;helmet&quot;)
const socket = require(&quot;socket.io&quot;)
const http = require(&quot;http&quot;)
const compression = require(&quot;compression&quot;)
const bodyParser = require(&quot;body-parser&quot;)
const cookieSession = require(&quot;cookie-session&quot;)
const cookieParser = require(&quot;cookie-parser&quot;)

// IMPORT ROUTES
const routes = require(&quot;./routes&quot;)

// IMPORT MIDDLWARES
const Import = require(&quot;@archsplace/archsplace_commun&quot;)
const isError = Import(&quot;middlewares&quot;, &quot;isError&quot;)
const isCurrentUser = Import(&quot;middlewares&quot;, &quot;isCurrentUser&quot;)
const { NotFoundError } = Import(&quot;factory&quot;, &quot;errors&quot;)

// LAUNCH EXPRESS
const app = express()
const server = http.createServer(app)
const io = socket(server)

const secure = process.env.NODE_ENV !== &quot;test&quot;

// USE MAIN MIDDLWWARE
app.set(&quot;trust proxy&quot;, true)
app.use(helmet())
app.disable(&quot;x-powered-by&quot;)
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
app.use(cookieSession({ signed: false, secure }))
app.use(isCurrentUser)
app.use(cookieParser())
app.use(compression())
app.use((req, res, next) =&gt; {
  req.io = io
  next()
})

// USE ROUTES
routes.map(route =&gt; app.use(route.url, route.path))
app.all(&quot;*&quot;, async (req, res) =&gt; {
  throw new NotFoundError()
})

// USE CUSTOM MIDDLWWARE
app.use(isError)

module.exports = server
</code></pre>
<p>Then i have a endpoint to emit a chat event, its the same endpoint i use to POST my message in my mongoDB database. I think this part work somehow, but not sure where it's emitting exactly</p>
<p><strong>ENDPOINT TO EMIT SOCKET EVENT</strong></p>
<pre><code>const express = require(&quot;express&quot;)
const db = require(&quot;mongoose&quot;)

// DATABASE AND LIBRARIES
const Import = require(&quot;@archsplace/archsplace_commun&quot;)
const isAuthenticated = Import(&quot;middlewares&quot;, &quot;isAuthenticated&quot;)
const isActivated = Import(&quot;middlewares&quot;, &quot;isActivated&quot;)
const Chat = Import(&quot;models&quot;, &quot;Chat&quot;, &quot;chat&quot;)
const Room = Import(&quot;models&quot;, &quot;ChatRoom&quot;, &quot;chat&quot;)

// EVENTS
const { NatsWrapper } = require(&quot;../../../services/natsWrapper&quot;)
const { RoomUpdatedPub } = require(&quot;../../../events/publishers/roomUpdatedPub&quot;)
const { ChatCreatedPub } = require(&quot;../../../events/publishers/chatCreatedPub&quot;)

// VALIDATES
const { BadRequestError, DatabaseConnectionError } = Import(&quot;factory&quot;, &quot;errors&quot;)

const router = express.Router()

// @route  POST api/chat/private/message/:roomId
// @desc   Post Chat message by chatroom id
// @access Private
router.post(&quot;/:roomId&quot;, isAuthenticated, isActivated, async (req, res) =&gt; {
  // DEFINE QUERIES
  let chat
  const { message, avatar } = req.body
  const { roomId } = req.params

  // ENSURE ROOM EXIST FOR USER
  const room = await Room.findOne({ $and: [{ _id: roomId }, { _users: { $elemMatch: { _user: req.user._id } } }] })
  if (!room) {
    throw new BadRequestError(&quot;This chatroom doesn't exist&quot;)
  }

  // CREATE CHAT
  const chatFields = {
    message,
    _emitter: req.user._id,
    _chatId: roomId
  }

  // EMIT TO SOCKET
  req.io.emit(roomId, {
    ...chatFields,
    avatar: avatar,
    read: 1,
    role: req.user.authorities
  })

  // HANDLE MONGODB TRANSACTIONS
  const SESSION = await db.startSession()
  try {
    // CREATE CHAT
    await SESSION.startTransaction()
    chat = await new Chat(chatFields).save()
    await room.set({ lastUpdated: Date.now() }).save()
    await new RoomUpdatedPub(NatsWrapper.client()).publish(room)
    await new ChatCreatedPub(NatsWrapper.client()).publish(chat)
    await SESSION.commitTransaction()

    // RETURN AND FINALIZE ENDPOINT
    res.status(201).json(chat)
  } catch (e) {
    // CATCH ANY ERROR DUE TO TRANSACTION
    await SESSION.abortTransaction()
    console.error(e)
    throw new DatabaseConnectionError()
  } finally {
    // FINALIZE SESSION
    SESSION.endSession()
  }
})

module.exports = router
</code></pre>
<p><strong>INGRESS NGINX CONGIS</strong></p>
<p>This probably could be wrong, i saw in internet that we need to use this annotation nginx.ingress.kubernetes.io/websocket-services and i am attributing to the server where i build the chat (and where i am using socket.io)</p>
<pre><code>apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: ingress-service
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/use-regex: &quot;true&quot;
    nginx.ingress.kubernetes.io/permanent-redirect-code: &quot;301&quot;
    nginx.ingress.kubernetes.io/from-to-www-redirect: &quot;true&quot;
    # SOCKET CONFIGURATIONS
    nginx.ingress.kubernetes.io/websocket-services: &quot;chat-srv&quot;
    nginx.ingress.kubernetes.io/proxy-send-timeout: &quot;1800&quot;
    nginx.ingress.kubernetes.io/proxy-read-timeout: &quot;1800&quot;
spec:
  rules:
    - host: www.archsplace.dev
      http:
        paths:
          - path: /api/architect/?(.*)
            backend:
              serviceName: architect-srv
              servicePort: 3000
          - path: /?(.*)
            backend:
              serviceName: client-website-srv
              servicePort: 3000
    - host: architects.archsplace.dev
      http:
        paths:
          - path: /auth/?(.*)
            backend:
              serviceName: auth-srv
              servicePort: 3000
          - path: /api/account/?(.*)
            backend:
              serviceName: account-srv
              servicePort: 3000
          - path: /api/architect/?(.*)
            backend:
              serviceName: architect-srv
              servicePort: 3000
          - path: /api/chat/?(.*)
            backend:
              serviceName: chat-srv
              servicePort: 3000
          - path: /?(.*)
            backend:
              serviceName: client-architects-srv
              servicePort: 3000
    - host: users.archsplace.dev
      http:
        paths:
          - path: /auth/?(.*)
            backend:
              serviceName: auth-srv
              servicePort: 3000
          - path: /api/account/?(.*)
            backend:
              serviceName: account-srv
              servicePort: 3000
          - path: /api/architect/?(.*)
            backend:
              serviceName: architect-srv
              servicePort: 3000
          - path: /api/chat/?(.*)
            backend:
              serviceName: chat-srv
              servicePort: 3000
          - path: /?(.*)
            backend:
              serviceName: client-users-srv
              servicePort: 3000
    - host: partners.archsplace.dev
      http:
        paths:
          - path: /auth/?(.*)
            backend:
              serviceName: auth-srv
              servicePort: 3000
          - path: /api/account/?(.*)
            backend:
              serviceName: account-srv
              servicePort: 3000
          - path: /api/chat/?(.*)
            backend:
              serviceName: chat-srv
              servicePort: 3000
          - path: /?(.*)
            backend:
              serviceName: client-partners-srv
              servicePort: 3000
    - host: admin.archsplace.dev
      http:
        paths:
          - path: /auth/?(.*)
            backend:
              serviceName: auth-srv
              servicePort: 3000
          - path: /api/account/?(.*)
            backend:
              serviceName: account-srv
              servicePort: 3000
          - path: /api/architect/?(.*)
            backend:
              serviceName: architect-srv
              servicePort: 3000
          - path: /api/chat/?(.*)
            backend:
              serviceName: chat-srv
              servicePort: 3000
          - path: /?(.*)
            backend:
              serviceName: client-admin-srv
              servicePort: 3000
    - host: business.archsplace.dev
      http:
        paths:
          - path: /?(.*)
            backend:
              serviceName: client-business-srv
              servicePort: 3000
</code></pre>
<p>Then in the client side i am using the library socket.io-client on my react app.</p>
<p><strong>CLIENT UTIL WHERE I DECLARE THE IO LIBRARY</strong></p>
<p>There since i use architects.archsplace.dev, i assume that the chat server would be only available at /api/chat as it's defined on ingress nginx but i am not sure..</p>
<pre><code>import io from &quot;socket.io-client&quot;

export const socket = io(`${window.location.host}/api/chat`, {
  reconnect: true
})
</code></pre>
<p>Then i build a UI with the chat where i am actually trying to receive the chat info and store it into a react state:</p>
<p><strong>REACT COMPONENT WITH USEEFFECT TO LOAD SOCKETS</strong></p>
<p>Here also you can see in the use effect i am using the roomId to connect to the same event i emit in the backend part previously. (this was working on a monolithic but not here)</p>
<pre><code>import React, { useState, useEffect } from &quot;react&quot;
import { connect } from &quot;react-redux&quot;
import Timestamp from &quot;react-timestamp&quot;

// IMPORT ACTIONS
import { sendMessage, getMessages } from &quot;@actions/chatActions&quot;

// IMPORT COMPONENTS
import ChatMessage from &quot;./ChatMessage&quot;

// IMPORT UTILS
import { socket, isEmpty, imageRender } from &quot;@utils&quot;

const ChatRoom = ({ sendMessage, getMessages, classes, room, user, chat: { messages } }) =&gt; {
  // HOOKS
  const [state, setState] = useState({
    message: &quot;&quot;,
    messages: [],
    errors: {},
    typing: false,
    trigger: false,
    isInteracted: false,
    page: 0,
    limit: 20
  })
  const target = room.users.find(({ _id }) =&gt; _id !== user._id)
  const isOnline = Math.floor(Date.now() - new Date(target.lastConnectionDate).getTime() / 1000) &amp;&amp; target.isOnline

  // USE EFFECT
  useEffect(() =&gt; {
    getMessages(state.page, state.limit, room._id)
  }, [getMessages, state.page, state.limit, room._id])

  useEffect(() =&gt; {
    const handleMessageSocket = () =&gt; {
      const addMessage = data =&gt; setState(prevStates =&gt; ({ ...prevStates, messages: [...state.messages, data] }))
      socket.on(room._id, data =&gt; addMessage(data))
    }
    const clearMessageSocket = () =&gt; {
      socket.off(room._id)
      setState(prevStates =&gt; ({ ...prevStates, messages: [] }))
    }
    // LOAD DATA FROM REDUCER
    setState(prevStates =&gt; ({ ...prevStates, messages }))
    // LOAD SOCKETS
    handleMessageSocket()
    return () =&gt; clearMessageSocket()
  }, [room._id, state.messages, messages])

  // HANDLE FUNCTIONS
  const handleMessage = e =&gt; setState(prevStates =&gt; ({ ...prevStates, message: e.target.value }))
  const clearMessage = () =&gt; setState(prevStates =&gt; ({ ...prevStates, message: &quot;&quot; }))
  const handleSubmit = e =&gt; {
    e.preventDefault()
    const chatMessage = { message: state.message, avatar: imageRender(user.avatar, &quot;tr:n-user_avatar_small&quot;) }
    !isEmpty(state.message) &amp;&amp; sendMessage(chatMessage, room._id)
    clearMessage()
  }
  // RENDER CHATROOM ITEM
  const renderAvatar = () =&gt; {
    return (
      &lt;div className={classes.chatRoomItemAvatar}&gt;
        &lt;img src={imageRender(target.avatar, &quot;tr:n-user_avatar_small&quot;)} alt={target.name} /&gt;
        {isOnline &amp;&amp; &lt;div className={classes.chatRoomItemActive} /&gt;}
      &lt;/div&gt;
    )
  }
  const renderInfo = () =&gt; {
    return (
      &lt;div className={classes.chatRoomItemInfo}&gt;
        &lt;h3&gt;{target.name}&lt;/h3&gt;
        &lt;p&gt;
          &lt;i&gt;access_time&lt;/i&gt;
          &lt;Timestamp className=&quot;request-item-timestamp&quot; relative date={target.lastConnectionDate} autoUpdate /&gt;
        &lt;/p&gt;
      &lt;/div&gt;
    )
  }
  const renderChatItem = () =&gt; {
    return (
      &lt;div className={classes.chatRoomItem}&gt;
        {renderAvatar()}
        {renderInfo()}
      &lt;/div&gt;
    )
  }
  // RENDER MESSAGE AREA
  const renderMessages = () =&gt; {
    return &lt;div className={classes.chatMessages}&gt;{JSON.stringify(state.messages.map(i =&gt; i.message))}&lt;/div&gt;
  }

  // RENDER INPUT AREA
  const renderInput = () =&gt; {
    return (
      &lt;form className={classes.chatInputWrapper} autoComplete=&quot;off&quot; onSubmit={e =&gt; handleSubmit(e)}&gt;
        &lt;div className={classes.chatInput}&gt;
          &lt;input type=&quot;text&quot; placeholder=&quot;Message&quot; value={state.message} onChange={handleMessage} /&gt;
          &lt;button type=&quot;submit&quot;&gt;
            &lt;i&gt;reply&lt;/i&gt;
          &lt;/button&gt;
        &lt;/div&gt;
      &lt;/form&gt;
    )
  }

  // MAIN
  return (
    &lt;div className={classes.chatRoom}&gt;
      {renderChatItem()}
      {renderMessages()}
      {renderInput()}
    &lt;/div&gt;
  )
}

const mapStateToProps = state =&gt; ({
  chat: state.chat
})

export default connect(mapStateToProps, { sendMessage, getMessages })(ChatRoom)
</code></pre>
<p>So if anyone have been confronted to the same issue, and know what i might have done wrong, i am  completly stuck. I even tried to setup a redis service and pass the socket throught a redis io adapter but didn't work either...</p>

Erwan A. R. Riou
<p>I found a solution there for the people that might struggle like me...i think it's a bit of hacky but it work well.</p>
<p>I was observing in my front that the socket were all the time triggering under the /socket.io/.... and if you take a look at my ingress nginx that would look into my react app and return a 404 page probably.</p>
<p>So i forced my chat-srv to be present there on this specific endpoint with the following code:</p>
<pre><code> - path: /socket.io/?(.*)
   backend:
     serviceName: chat-srv
     servicePort: 3000
</code></pre>
<p>Then once i did this, it was actually looking into my chat backend and worked out. I need also to specify that i use the version 2.2.0 of socket.io</p>
<p>I tried with the version 3 but it didn't work.</p>
