• Download
  • Contact
  • Terms of Service
  • Privacy Policy
  • About US
Codershood
  • Demos
  • Plugins
  • Angular
  • NodeJs
  • GO lang
  • Others
No Result
View All Result
Codershood
  • Demos
  • Plugins
  • Angular
  • NodeJs
  • GO lang
  • Others
No Result
View All Result
Codershood
No Result
View All Result

Sending message to specific user with GoLang WebSocket

by Shashank Tiwari
August 6, 2023
in GO lang
1
12 Minutes Read
Sending message to specific user with GoLang WebSocket

In the last tutorial, we created a Group chat where multiple users chat with each other. In this post, we will go one step further and we will be Sending message to specific user with GoLang WebSocket. This feature will later help us to create a private chat application using GoLang.

But this article is truly based on Sending message to the specific user with GoLang WebSocket and I will explain how we can do that in GoLang WebSocket.

 

 Download

 




 

1. Sending message to the specific user with the GoLang WebSocket workflow

Here, We will create a map in the socket server in which we will store all the connected users to the socket server. When users connect to the Socket server, then the Socket server sends an array of objects to the client as a list of online users. This object contains the unique user id as well as username which is provided by the client when a client connects to the server.

for example,[ {id:'some randon id',name:'Shashank'},{...},{...}....{n}];

Note: In this application, I am using a map to store the users connected to the server, But In production please avoid this idea instead use your choice of database to store the user’s information.

The below Image shows how we are storing information of users connected to the server.

Herennumber of users connected to the server, we are storing their respective user ID (we generate the user id using uuid), name, and throwing back the array of objects to all clients connected to the server.

Sending message to specific user with GoLang WebSocket step 1

 

To send a message to the particular client, we are must provide the user id of that client to the server. In GoLang Socket Server, We will search for that user using that user’s user-id as shown below.

        
func EmitToSpecificClient(hub *Hub, payload SocketEventStruct, userID string) {
    for client := range hub.clients {
        if client.userID == userID {
            select {
            case client.send <- payload:
            default:
                close(client.send)
                delete(hub.clients, client)
            }
        }
    }
}

 

For example, user 3 wants to send a message to user 1. Assuming user3 is contacted from the Chrome browser and user1 is connected to the Firefox browser as shown below image.

Sending message to specific user with GoLang WebSocket step 2Here, managing the list of users plays a very important role in terms of showing online users in the chat room. For example, when one user goes offline other users should get an updated list of online users. The figure below explains the same have a look,

 

Sending message to specific user with GoLang WebSocket step 3

 

Also, readSending a message to a specific user with socket.io

2. Creating a new GoLang application

=>Let’s start off by creating a new GoLang project by usinggo mod init specific-chatcommand. This command will initialize a new module in the current directory.

=>Also, if you notice the above command will create a new mod file named asgo.modin the current directory.

=>In this file you will have all your package listed, for now, this is how my mod file looks like,

go.mod:

        
module specific-chat

go 1.13

require (
    github.com/google/uuid v1.1.1
    github.com/gorilla/mux v1.7.4
    github.com/gorilla/websocket v1.4.2
)

3. Understanding the project structure

Here we will give a very little bit of styling to our web application just to make it look presentable. On the front-end, we are using React but here we won’t be usingcreate-react-appCLI. Instead of that, we will use CDN files and Babel Transpiler JS files. Inside thepublicfolder, we will write down the Javascript scripts and inside view folder, we will write down the MARKUP.

Sending message to specific user with GoLang WebSocket Folder StructureIn the above image, you can see we have listed files created in the application. Below I have listed the purpose of each file, why we need them, let’s start from the top,

/handlers:This folder will have all the file which are responsible for Creating Socket Connections and sending socket messages.

hub.go:In this file, we will create the Socket Hub where we will have information about all the users.

routes-handlers.go:Here we will write handlers for application routes.

socket-handlers.go:In this file, we will handle the incoming socket connections and here we have written logic to send the messages.

structs.go:In this file, we will write all the structs that will be used in packagehandlers.

/public:In this folder, we will write React Logic to render messages and CSS styling.

routes.go:As the name suggests, this file will contain the endpoints that we will define in this application.

server.go:To create the Golang server we will use the server.go file.

4. Creating a GoLang Server

Create aserver.goin the root of the project, which will be our entry point for the project. Here we will make a connection with the MongoDB database and we will define our application routes.

=>Inside the main() function, First we are printing some information.

=>In the next line, we will create route variable, which will hold Route instance.

=>Then AddApproutes() function register application routes.

=>And at the end, using http.ListenAndServe() we will start our GO server.

server.go:

        
// Sending message to specific user with GoLang WebSocket
// @author Shashank Tiwari

package main

import (
    "log"
    "net/http"

    "github.com/gorilla/mux"
)

func main() {

    log.Println("Server will start at http://localhost:8000/")

    route := mux.NewRouter()

    AddApproutes(route)

    log.Fatal(http.ListenAndServe(":8000", route))
}

5. Adding GoLang routes in the application

Create aroutes.goin the root of the project, Here we will register application routes. Here we will use gorilla/mux package to register routes.

=>Then AddApproutes() function will register all the routes in the application. Here we have only one route to add which will be used by the FrontEnd javascript.

routes.go:

        
// Sending message to specific user with GoLang WebSocket
// @author Shashank Tiwari

package main

import (
    "log"
    "net/http"

    "github.com/gorilla/mux"
    "github.com/gorilla/websocket"

    handlers "specific-chat/handlers"
)

func setStaticFolder(route *mux.Router) {
    fs := http.FileServer(http.Dir("./public/"))
    route.PathPrefix("/public/").Handler(http.StripPrefix("/public/", fs))
}

// AddApproutes will add the routes for the application
func AddApproutes(route *mux.Router) {

    log.Println("Loadeding Routes...")

    setStaticFolder(route)

    hub := handlers.NewHub()
    go hub.Run()

    route.HandleFunc("/", handlers.RenderHome)

    route.HandleFunc("/ws/{username}", func(responseWriter http.ResponseWriter, request *http.Request) {
        var upgrader = websocket.Upgrader{
            ReadBufferSize:  1024,
            WriteBufferSize: 1024,
        }

        // Reading username from request parameter
        username := mux.Vars(request)["username"]

        // Upgrading the HTTP connection socket connection
        connection, err := upgrader.Upgrade(responseWriter, request, nil)
        if err != nil {
            log.Println(err)
            return
        }

        handlers.CreateNewSocketUser(hub, connection, username)

    })

    log.Println("Routes are Loaded.")
}

    

Explanation:

1.setStaticFolder function will set the static folder path.

2.After that, we start our socket server Hub, which contains all the users connected to the socket server.

3./ API will render the Home of the Group Chat.

4./ws/{username}/ API will initiate a Socket connection. This Route will also create a new User in the Socket server.

5.We read theusernamefrom the parameter usingGorilla MUXrouting, here you can pass any name for the user when creating a new socket connection.

6.The API /ws/{username}/ calls the anonymous function, which first upgrades the HTTP server connection to the WebSocket protocol. You can find the original definitionshere, for upgrading the HTTP server connection to the WebSocket protocol.

7.Then we callCreateNewSocketUser()function from the handler package, which creates a new socket connection.

6. Creating a Hub for Socket Server

Create ahub.gounder/handlersfolder, here we will write a code to create socket Hub. The code written inside this file is called from theroute-handler.gofile as saw in the above explanation.

/handlers/hub.go:

        
package handlers

// Hub maintains the set of active clients and broadcasts messages to the clients.
type Hub struct {
    clients    map[*Client]bool
    register   chan *Client
    unregister chan *Client
}

// NewHub will will give an instance of an Hub
func NewHub() *Hub {
    return &Hub{
        register:   make(chan *Client),
        unregister: make(chan *Client),
        clients:    make(map[*Client]bool),
    }
}

// Run will execute Go Routines to check incoming Socket events
func (hub *Hub) Run() {
    for {
        select {
        case client := <-hub.register:
            HandleUserRegisterEvent(hub, client)

        case client := <-hub.unregister:
            HandleUserDisconnectEvent(hub, client)
        }
    }
}

    

Explanation:

1.The functionNewHub()returns the new instance of the Hub

2.Then we haveRun()function, which is a Goroutine. In this Goroutine, we receive data from the←registerand←unregisterchannel.

3.The←registerand←unregisterchannels call theHandleUserRegisterEvent()andHandleUserDisconnectEvent()respectively of the samehandlerpackage.

7. Creating new users and Sending messages

Create asocket-handler.gounder/handlersfolder, here we write code for creating a new user in the socket server and we will write a code to send messages across all users connected to the socket server.

/handlers/socket-handler.go:

        
// Sending message to specific user with GoLang WebSocket
// @author Shashank Tiwari

package handlers

import (
    "bytes"
    "encoding/json"
    "log"
    "time"

    "github.com/google/uuid"
    "github.com/gorilla/websocket"
)

const (
    writeWait      = 10 * time.Second
    pongWait       = 60 * time.Second
    pingPeriod     = (pongWait * 9) / 10
    maxMessageSize = 512
)

// CreateNewSocketUser creates a new socket user
func CreateNewSocketUser(hub *Hub, connection *websocket.Conn, username string) {
    uniqueID := uuid.New()
    client := &Client{
        hub:                 hub,
        webSocketConnection: connection,
        send:                make(chan SocketEventStruct),
        username:            username,
        userID:              uniqueID.String(),
    }

    go client.writePump()
    go client.readPump()

    client.hub.register <- client
}

// HandleUserRegisterEvent will handle the Join event for New socket users
func HandleUserRegisterEvent(hub *Hub, client *Client) {
    hub.clients[client] = true
    handleSocketPayloadEvents(client, SocketEventStruct{
        EventName:    "join",
        EventPayload: client.userID,
    })
}

// HandleUserDisconnectEvent will handle the Disconnect event for socket users
func HandleUserDisconnectEvent(hub *Hub, client *Client) {
    _, ok := hub.clients[client]
    if ok {
        delete(hub.clients, client)
        close(client.send)

        handleSocketPayloadEvents(client, SocketEventStruct{
            EventName:    "disconnect",
            EventPayload: client.userID,
        })
    }
}

// EmitToSpecificClient will emit the socket event to specific socket user
func EmitToSpecificClient(hub *Hub, payload SocketEventStruct, userID string) {
    for client := range hub.clients {
        if client.userID == userID {
            select {
            case client.send <- payload:
            default:
                close(client.send)
                delete(hub.clients, client)
            }
        }
    }
}

// BroadcastSocketEventToAllClient will emit the socket events to all socket users
func BroadcastSocketEventToAllClient(hub *Hub, payload SocketEventStruct) {
    for client := range hub.clients {
        select {
        case client.send <- payload:
        default:
            close(client.send)
            delete(hub.clients, client)
        }
    }
}

func handleSocketPayloadEvents(client *Client, socketEventPayload SocketEventStruct) {
    var socketEventResponse SocketEventStruct
    switch socketEventPayload.EventName {
    case "join":
        log.Printf("Join Event triggered")
        BroadcastSocketEventToAllClient(client.hub, SocketEventStruct{
            EventName: socketEventPayload.EventName,
            EventPayload: JoinDisconnectPayload{
                UserID: client.userID,
                Users:  getAllConnectedUsers(client.hub),
            },
        })

    case "disconnect":
        log.Printf("Disconnect Event triggered")
        BroadcastSocketEventToAllClient(client.hub, SocketEventStruct{
            EventName: socketEventPayload.EventName,
            EventPayload: JoinDisconnectPayload{
                UserID: client.userID,
                Users:  getAllConnectedUsers(client.hub),
            },
        })

    case "message":
        log.Printf("Message Event triggered")
        selectedUserID := socketEventPayload.EventPayload.(map[string]interface{})["userID"].(string)
        socketEventResponse.EventName = "message response"
        socketEventResponse.EventPayload = map[string]interface{}{
            "username": getUsernameByUserID(client.hub, selectedUserID),
            "message":  socketEventPayload.EventPayload.(map[string]interface{})["message"],
            "userID":   selectedUserID,
        }
        EmitToSpecificClient(client.hub, socketEventResponse, selectedUserID)
    }
}

func getUsernameByUserID(hub *Hub, userID string) string {
    var username string
    for client := range hub.clients {
        if client.userID == userID {
            username = client.username
        }
    }
    return username
}

func getAllConnectedUsers(hub *Hub) []UserStruct {
    var users []UserStruct
    for singleClient := range hub.clients {
        users = append(users, UserStruct{
            Username: singleClient.username,
            UserID:   singleClient.userID,
        })
    }
    return users
}

func (c *Client) readPump() {
    var socketEventPayload SocketEventStruct

    defer unRegisterAndCloseConnection(c)

    setSocketPayloadReadConfig(c)

    for {
        _, payload, err := c.webSocketConnection.ReadMessage()

        decoder := json.NewDecoder(bytes.NewReader(payload))
        decoderErr := decoder.Decode(&socketEventPayload)

        if decoderErr != nil {
            log.Printf("error: %v", decoderErr)
            break
        }

        if err != nil {
            if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
                log.Printf("error ===: %v", err)
            }
            break
        }

        handleSocketPayloadEvents(c, socketEventPayload)
    }
}

func (c *Client) writePump() {
    ticker := time.NewTicker(pingPeriod)
    defer func() {
        ticker.Stop()
        c.webSocketConnection.Close()
    }()
    for {
        select {
        case payload, ok := <-c.send:
            reqBodyBytes := new(bytes.Buffer)
            json.NewEncoder(reqBodyBytes).Encode(payload)
            finalPayload := reqBodyBytes.Bytes()

            c.webSocketConnection.SetWriteDeadline(time.Now().Add(writeWait))
            if !ok {
                c.webSocketConnection.WriteMessage(websocket.CloseMessage, []byte{})
                return
            }

            w, err := c.webSocketConnection.NextWriter(websocket.TextMessage)
            if err != nil {
                return
            }

            w.Write(finalPayload)

            n := len(c.send)
            for i := 0; i < n; i++ {
                json.NewEncoder(reqBodyBytes).Encode(<-c.send)
                w.Write(reqBodyBytes.Bytes())
            }

            if err := w.Close(); err != nil {
                return
            }
        case <-ticker.C:
            c.webSocketConnection.SetWriteDeadline(time.Now().Add(writeWait))
            if err := c.webSocketConnection.WriteMessage(websocket.PingMessage, nil); err != nil {
                return
            }
        }
    }
}

func unRegisterAndCloseConnection(c *Client) {
    c.hub.unregister <- c
    c.webSocketConnection.Close()
}

func setSocketPayloadReadConfig(c *Client) {
    c.webSocketConnection.SetReadLimit(maxMessageSize)
    c.webSocketConnection.SetReadDeadline(time.Now().Add(pongWait))
    c.webSocketConnection.SetPongHandler(func(string) error { c.webSocketConnection.SetReadDeadline(time.Now().Add(pongWait)); return nil })
}

    

Explanation:

Let’s start from the Top,

1.The first function isCreateNewSocketUser(), this function will create a new user/client. After creating a new user we will send this user toregister←channel.

2.Next, we haveHandleUserRegisterEvent()andHandleUserDisconnectEvent()function, which will callhandleSocketPayloadEvents(). These methods will handlejoinanddisconnectsocket event.

3. EmitToSpecificClient function takes 3 args hub, socket payload, and userID. Based on the userID provided, it sends socket Payload to that user, whos userId matches with the userId of users stored in socket server.

4.TheBroadcastSocketEventToAllClient()function will send the socket events to all the users connected to Socket Server by using a←sendchannel.

5.FunctionhandleSocketPayloadEvents()will send the Socket Event to the connected user based on the Event type. In this function, we have writtenswitch casestatement to send the valid socket response based on the socket events.

6.Then we havereadPump()andwritePump()functions, these functions are running as Goroutines. If you notice we have called these functions fromCreateNewSocketUser()function.

7.InreadPump()function, we first callunRegisterAndCloseConnection()as defer statement, then we callsetSocketPayloadReadConfig()function which sets the payload read configuration from peers.

8.TheunRegisterAndCloseConnection()function closes the socket connection for on single user/client passed in the function parameter.

9.In the functionsetSocketPayloadReadConfig(), we set the read limit of incoming packet usingSetReadLimit()function. Ths functionSetReadDeadline()will set the read deadline on the underlying network connection. After a read has timed out, the WebSocket connection state will be corrupted and all future reads will return an error.

10.And then after reading and decoding the message we pass the socket event to thehandleSocketPayloadEvents()function, which further delegates the socket event to the users based on the event type.

11.InwritePump()function, we first call defer function close the socket connection. Then we read the socket event from the←sendchannel. With the help of theNextWriter()function, we get an instance of a Web socket response writer. On top of this Web socket response writer, we call the functionWrite()to send the socket payload to the socket connection.

8. The Frontend: Creating A UI with React

First thing first create anindex.htmlfile under views folder. In this file, we will import the Library of React and Babel, so that our application will have the capability to handle react code.

Also, here we will include style.css file in order to add styling of the Page. We won’t talk much about it since it is not that interesting topic here. Open theindex.htmlfile and write below markup,

views/index.html:

        
<!DOCTYPE html>
<html lang="en">

<head>
    <title>Chat Example</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <link href="/public/css/style.css" rel="stylesheet" type="text/css" />
    <script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>

<body>
    <div class="chat__app-container"></div>
    <script src="/public/js/script.js" type="text/babel"></script>
</body>

</html>
    

Explanation:

1.First, we have addedstyle.cssfile, which will style our Page.

2.Second, we have added React and Babel libraries, so our webpage will have the capability to compile the react code.

3.We have created a root element where we will render all our Virtual Dom with the help of React.

4.Lastly, we have added ascript.jsfile that contains the code of React to render the chat screen n all.

Now createscript.jsfile under/public/jsfolder, As I told you just now that this file will have code written in React in order to render the Chat Screen.

/public/js/script.js:

        
// Sending message to specific user with GoLang WebSocket
// @author Shashank Tiwari

const domElement = document.querySelector(".chat__app-container");

class App extends React.Component {
    constructor() {
        super();
        this.state = {
            chatUserList: [],
            message: null,
            selectedUserID: null,
            userID: null
        }
        this.webSocketConnection = null;
    }

    componentDidMount() {
        this.setWebSocketConnection();
        this.subscribeToSocketMessage();
    }

    setWebSocketConnection() {
        const username = prompt("What's Your name");
        if (window["WebSocket"]) {
            const socketConnection = new WebSocket("ws://" + document.location.host + "/ws/" + username);
            this.webSocketConnection = socketConnection;
        }
    }

    subscribeToSocketMessage = () => {
        if (this.webSocketConnection === null) {
            return;
        }

        this.webSocketConnection.onclose = (evt) => {
            this.setState({
                message: 'Your Connection is closed.',
                chatUserList: []
            });
        };

        this.webSocketConnection.onmessage = (event) => {
            try {
                const socketPayload = JSON.parse(event.data);
                switch (socketPayload.eventName) {
                    case 'join':
                    case 'disconnect':
                        if (!socketPayload.eventPayload) {
                            return
                        }

                        const userInitPayload = socketPayload.eventPayload;

                        this.setState({
                            chatUserList: userInitPayload.users,
                            userID: this.state.userID === null ? userInitPayload.userID : this.state.userID
                        });

                        break;

                    case 'message response':

                        if (!socketPayload.eventPayload) {
                            return
                        }

                        const messageContent = socketPayload.eventPayload;
                        const sentBy = messageContent.username ? messageContent.username : 'An unnamed fellow'
                        const actualMessage = messageContent.message;
                        
                        this.setState({
                            message: `${sentBy} says: ${actualMessage}`
                        });

                        break;

                    default:
                        break;
                }
            } catch (error) {
                console.log(error)
                console.warn('Something went wrong while decoding the Message Payload')
            }
        };
    }

    setNewUserToChat = (event) => {
        if (event.target && event.target.value) {
            if (event.target.value === "select-user") {
                alert("Select a user to chat");
                return;
            }
            this.setState({
                selectedUserID: event.target.value
            })   
        }
    }
    
    getChatList() {
        if (this.state.chatUserList.length === 0) {
            return(
                <h3>No one has joined yet</h3>
            )
        }
        return (
            <div className="chat__list-container">
                <p>Select a user to chat</p>
                <select onChange={this.setNewUserToChat}>
                    <option value={'select-user'} className="username-list">Select User</option>
                    {
                        this.state.chatUserList.map(user => {
                            if (user.userID !== this.state.userID) {
                                return (
                                    <option value={user.userID} className="username-list">
                                        {user.username}
                                    </option>
                                )
                            }
                        })
                    }
                </select>
            </div>
        );
    }

    handleKeyPress = (event) => {
        try {
            if (event.key === 'Enter') {
                if (!this.webSocketConnection) {
                    return false;
                }
                if (!event.target.value) {
                    return false;
                }

                this.webSocketConnection.send(JSON.stringify({
                    EventName: 'message',
                    EventPayload: {
                        userID: this.state.selectedUserID,
                        message: event.target.value
                    },
                }));

                event.target.value = '';
            }
        } catch (error) {
            console.log(error)
            console.warn('Something went wrong while decoding the Message Payload')
        }
    }
    getChatContainer() {
        return (
            <div class="chat__message-container">
                <div class="message-container">
                    {this.state.message}
                </div>
                <input type="text" id="message-text" size="64" autofocus placeholder="Type Your message" onKeyPress={this.handleKeyPress}/>
            </div>
        );
    }

    render() {
        return (
            <React.Fragment>
                {this.getChatList()}
                {this.getChatContainer()}
            </React.Fragment>
        );
    }
}

ReactDOM.render(<App />, domElement)
    

Explanation:

1.Let’s start from the Bottom, i.e.render()method, Here we have to return obviously JSX which enclosed by React Fragment. In this React Fragment, we have called getChatList()and getChatContainer() methods.

2. The getChatList() method will render the select tag from which we can select a single user with whom we want to chat.

3. In the method getChatContainer() , we will render the messages received from other users. Next, we have an input box to send messages to other users.

4.In getChatContainer()method, we consumemessagesproperty of thestateobject. Themessagesproperty will be an array that will have an object with keysusernameandmessage.

5.Above that, we havehandleKeyPress()method, which will be triggered every time when you will type anything in the input box. This method is responsible to send the messages to the socket server.

6. As I said before the getChatList() method renders select tag, which will contain a list of users. Here we use chatUserList state property to render the chat list. Whenever we will select any user from the select tag, we will call setNewUserToChat() method.

7. InsetNewUserToChat() method, we update the selectedUserID property of state object by using setState.

8.UsingwebSocketConnectionproperty of the App Component, we will send the message to the Web Socket server. The propertywebSocketConnectionholds an instance of the Web Socket connection, which initialized in thecomponentDidMountlifecycle method.

9.The methodssetWebSocketConnection()andsubscribeToSocketMessage()is called fromcomponentDidMountlifecycle method. Names of both methods are self-explaining their purpose, which we will discuss below anyway.

10.The methodsetWebSocketConnection()will set up a Socket connection and save its instance to thewebSocketConnectionproperty of the App Component class.

11.InsubscribeToSocketMessage()method we are subscribing to the incoming socket events from web socket and based on their type we are pushing the events intomessagesproperty of the state object by usingsetState()method.

9. Running the application: Moment of Truth

To run the application, you need to build the application and then run the executable file as shown below,

> go build
> ./specific-chat

Now you can test this application by opening the localhost:8000.

10. Conclusion

For now, That’s it. Now using this article you canSend message to specific users with GoLang WebSocketand you can integrate this feature in the realtime private chat application. In the next post, we start building a Realtime private chat application in GoLang, where this feature will be useful.

If you have any suggestions, questions, or feature requests, let me know in the below comment box, I would be happy to help. If you like this article, do spread a word about it and share it with others.

Tags: ChatGoGo serverGolangGolang chatPrivate chat
Previous Post

Build a Realtime group chat app in Golang using WebSockets

Next Post

Real time private chatting app using GoLang, React and Mongodb – Part 1

Related Posts

Real time private chatting app using React, Golang and mongodb banner-part 2
GO lang

Real time private chatting app using GoLang, React and mongodb – Part 2

July 4, 2020
Real time private chatting app using React, Golang and mongodb banner
GO lang

Real time private chatting app using GoLang, React and Mongodb – Part 1

July 4, 2020
Build a Realtime group chat app in Golang using WebSockets
GO lang

Build a Realtime group chat app in Golang using WebSockets

June 17, 2020
Next Post
Real time private chatting app using React, Golang and mongodb banner

Real time private chatting app using GoLang, React and Mongodb – Part 1

Comments 1

  1. RC says:
    5 years ago

    Cannot wait to see part 3 What time is it gonna be live?

    Reply

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *




https://codershood.info

www.codershood.info programming blog dedicated to providing high-quality coding tutorial and articles on web development, Angular, React, Laravel, AngularJs, CSS, Node.js, ExpressJs and many more. We also provide ebook based on complicated web application along with the source code.

  • Download
  • Contact
  • Terms of Service
  • Privacy Policy
  • About US

www.codershood.info is licensed under a Creative Commons Attribution 4.0 International License.

No Result
View All Result
  • Demos
  • Plugins
  • Angular
  • NodeJs
  • GO lang
  • Others

www.codershood.info is licensed under a Creative Commons Attribution 4.0 International License.