Building a Chat with WebSockets and HTMX in Golang

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
all right guys so in this video we are going to be building a concurrent shot applications from scratch using goang and HTM X I've decided to make this video because I think it's an excellent way to learn concurrency and go if you're not familiar with it while building a cool project that I think is going to be a good starting point for any of your future projects as well as of the time of recording this video I couldn't find many references on how to build with websockets using HDMX so this is the service that you're going to be building and the Really the key Point here is that of course we're going to be building the concurrent chats and that has its business logic but the main difference between building for HDMX and building just a restful API that is going to have I don't know like react on the front end is here when we return from the server instead of returning Json you might be familiar with that so the server is going to be sending HTM X and on the front end we'll be replacing those bits and pieces on the UI dynamically so it's a bit of dynamic it's a bit of a different paradigm shift if you're not familiar with this uh it's basically the concept of ATS if you want to learn more about it so regarding what we are going to be building it's the service which is going to have two key components the clients and the Hub now for each socket connection coming into the wire the the server is going to create a client for each so you can see here two clients for two connections and the client is an is an intermediary and the client is a man in the middle between the the connection and the Hub right and then the Hub is the broadcaster of messages to the clients and H application State as well right so for example we're going to have the the connected clients it's going to be a list and then we're going to have as well the messages of the chats so here on my vs code just to give you a quick overview of the starting template that I have it is this main go which basically has an HTTP that is listening on part 2,000 and then I have two handlers that we're going to be building one for the serving the index. HTML and then one for the web circuit connection right so here on the templates I have an index HTML file which Imports the CDN for HTM X the CDN for the HTM X web sockets extensions so here I have a link if you want to learn more about it and then I just have here ta wins to do some stylings not that this is going to be pretty I want to be quick and simple to the points but yeah this is the HTML with some syntax from hmx so here we have a webs connection on this uh port on this endpoints and then here basically we have the the shat so everything inside here is going to be replaced in the future with messages right and then here we have a form which has the the W send which is going to be sending to this the closest web so connection which is this one up there right so we're going to be going going back to these templates in the bit but for now we have a lot to build um yeah let's go so here on the serve index what we can do is just do an HTTP do serve file and then we just serve the templates SL index.html this will do it I need to first pass the this here right but I want to add some validations here in case if the UR URL is different than slash we're going to do something the URL do path we are going to dra an error right not F and we can do an HTTP status not fonts I'm going to copy this piece of codes because we also need to check if the method is different than a get just a get for the index right so if we compile this so I also have here this mic file very simple just to have some shortcuts if I do M run so it was an error because you have a NE Handler okay so it's early enough but yeah we are serving the index we could comment this and I think we can test it like this so let's go here to Local Host 3000 and there we go so we have here the shots it's serving the index that is working then here for the web soet connection what we can do here is we just fill this and what we can do is that we need to serve the web sockets and initialize the Hub and the client so here is a connection coming through the wire and I'm going to create here a client. go and we can also create the hub. go these are the two files that we're going to have these are the two components that we saw as well on the diagram so let's start with the clients with this method here now this method is going to receive a hub that we currently don't have defined going to go back to it in a bit and then also an HTTP writer and request this is it then let's just go and find the Hub right now I think it's I think we can do that instruct so from the diagram we saw that we needed the connected clients um which is going to be for example a map of client we need here reference and then the blue in this is going to represent for example we have clients one and he's connected this is going to be true client two is's going to Connected it's going to be just a way to have some states of the connected coins and then we can also um Lo over them right then we're going to have three channels one of which is the broadcast not broadcast but broadcast which is going to be a poter to a message that we're going to Define as well the register every time someone registers he comes through this Channel and again it's going to be sent a client and the end register pretty simple pretty similar to the register one let me just go here and Define the client as well since we are already defining everything now the client is going to have a reference to the hub so here for the connection I'm going to be using the G websocket uh package that I have so it's this one gorilla.com I really like to use gal stuff so I'm going to be using this one and then channel of bites this is going to be a send so basically when the client wants to send something from the the browser so it's this part here when you send something we're going to use that channel and the Hub is going to be receiving uh a size of bites right so another thing that we can add for example is an ID so if we beong in shats we can have the username all of that I'm just going to have a randomly generated ID for this so back here on the Hub we also have one more thing to Define which is the message the message could have for example a client ID and the text right this is something that we might revisit in the future but for now it's this I just forgot to import gorilla so here on a serf web socket there is three things that you need to do it is to upgrade the connection we also need to create the clients and then we need to start listening to the to the basically so the first thing is that we need to upgrade our connection let me just goad ahead and do an app grader so this is standard for the the package that we're using just following the documentation you can get all these values so I'm just going to use some default values that I have usually recommend this bu size as well let's do this okay now here let's do the connection upgraders do upgrades right so here we need to send the response writer the reader and we can pass nil it's fine let's handle the error can just log it and then what we need to do is create the clients need clients for the clients we need the Hub reference that we have from the r parameter here we also need a connection that we already just created we need a send channel that we can create here on a f make a Channel of bites and finally we need an ID so the ID I'm going to be creating here how can we create this I think I'm going to be using a simple U ID do new so I'm going to be importing Google SLU ID and then you just have your ID as well okay so now that we have a created client who can notify the Hub so we can go client. hub. register and send through a message to that channel which the clients right so if you remember the register was receiving a pointer to a client and here we just got that so we can just say hey these user just got registers send them whatever you want so in this case we can send them the the shat history finally we need two methods which is going to be the right pump and the other one is going to be the read pump okay so let's go one by one let me just find them below here okay all right so let's start with the read PMP the read pump reads messages from the web so connection so this is what it's doing so we have the webset connection the client here is reading from the web so connection all the time so that's what you need to do first thing is don't forget to just defer this what we need to do here on this defer is just close the connection so let's go to the connection and here I think we have close yeah and uh yeah let's do it here as well I'm going to go to the hub to the unregistered and just going to say that we got unregistered so next we need to do some standard stuff when setting up the U socket which is to set the read limit let set it for example Max message size this is going to be a variable that you're going to go back to it then we need to set the connection dot sets reads deadline which you can do that time. now uh let me go write this again time do now I can type do add P wa right so this going to be a time that we're going to be setting from the variables as well and finally we need to set the connection pong Handler set P handler do we have an auto completion here we have awesome and and inside here what we do is again we access the connection and we set the reads deadline to be time do now do ID weights just like the above the line above and we can return nil as well because it expects an error you don't have one here all right so this is the variables that I have defined Max M and then the pong wait time so these values I just copied again from the documentation from the gorilla then let's go ahead and start listening for the read message so there's a method for the connection which is reads message this is going to read has bytes so what we have here is message type that you don't care about we have the text message and an error so we can just just for example print F and I want to the value and I'm going to stringify this text so we can just have some logs so here if there is an error what we can do is just we should handle this error it's recommended that we do it if you're going to production we can just continue here if you want to handle this what we can do here is this so from the documentation if it's an unexpected close we print it here we could just Panic we could do whatever you want but the idea is that we are breaking we are closing the connection from the user right so if you just want to go simple just do the continue if you want to um make this production level add some more error level uh logging and as well handle the error properly so now back to the message what we need to do here is that we need to pass the message and send to to the H so what's going to happen in the end is this so this is what want to do broadcast and we want to send um basically a message with the client ID and this is going to be the connection. ID and the text right text is what we want and if we just pass text this is not going to work because it's not off the type um that we want which is a string this is going to be a string of Json because from hmx what it does it's going to be sending here on the input when we send uh D send it's going to be sending an array uh an object with the text and header properties so let's have here the passed message I think I'm going to use this message as well the correct way would be to do something like this uh W socket message for example and here we have header interface anything so this is what they're going to be sending here is going to be the text let me just do Jason has a text and here I don't care about the header but we can just do headers and then here we can just do the headers like this so I'm going to use this type instead so that we don't confuse with the actual message and then let's create a reader bytes. reader because we are coming from bytes which is the text right yes so why is this not we need to do new reader then we need a decoder the Json decoder it's going to be json. new decoder and we pass in the reader then let's just do the decoding now that you have the decoder decoder of decodes put put it inside message and if there is an error let's just log. printf and say that errors V and errors okay and here what we can do is send the message do text so this is going to be sending the message from the connection to the hub we're going to be sending a message to the broadcast let's do the read pump it's not being in news I think it's because there's an error somewhere it's because this not being used so let's go back to the main goal okay so here on the main index not that we just created a serve websockets and we are we created the right pump as well so we just we created the read pump it just needs to define the right pump as well but we can go back and close this error so we needs to create the Hub so I'm going going to be making a hub here send new Hub and then we go back let me go back to the hub method oops and create a function called new Hub that is going to be returning a pointer to HUB like so so it requires client which we can just make map like so then we have the broadcast Channel we also have Auto completion for this the register and finally the unregistered so this is the The Hub so this should be working but now there's a thing that we need to do which is once go routine with the hub. run which is going to start listening for the the the broadcasting so let's create that method it is it is the only method from The Hub that we have run doesn't receive anything and here we can leave this empty for now right then here on the client let's finish up writing the right pump so again let's start by closing any connection if something bad happens here and we also need some extra Behavior here because we have a taking beh behavior on the on the right pump so we can do that by creating a ticker for the web socket so new ticker and we can pass in the pink period which we not have defined so let's go ahead and Define this on the constants here so the Ping period is going to be sending pings to the to the peers and it must be less than the Pongo right so I have this DEC commented here if you want to follow along I have the code in the description as well below so here the ticker um let's just start listening and then we handle that properly so the first thing let's do is Select if there is a message okay so we need to receive from the send here and let's do a connection sets right that line time do now adds and that's do the right weight another variable that you need to write and I think it's the last one so the right weight it is the time allows to write a message to the pier okay so here below where we were so if it's is not okay what we need to do is basically send an empty message um to to the client because what happened is that probably the Hub has been shut down so what you can do is just write message and have a uh code of Co message and then just send empty bites there it's fine so now at this point what we what we are is that we are reading a message that has been sent to us so let's do that so let's do connection do next writer and do a web socket dot text message because it's going to be a text message the is difference than nil return right then we can just do this right and send the message in as simple as that right and then at the end what we can do and what we need to do is close this T and handle the errors accordingly which we are not because we're wiy so one last thing that we can do here is more of a performance post which is add the cute messages from the shots to the current web soet mes so basically compile all of them into one big message and just send them so what you can do here is a for Loop um like this and the length of order the count is going to be basically the link of the channel messages and inside here we do is just write message right so we just writing the whole messages that are uh there so we might have server might be slow and then we can just send the M here instead of uh blocking it and here I've just added this little piece of codes which is for the ticker so just setting deadline and then we're writing the message with the Ping message for the clients that to know that we're alive okay so this is the right pump and the read the read pump as well so this client functionality is pretty much done now let's go to the back to the fun part and write the Run for the run here we also are going to be listening so let's have a for and select and these are the three channels that we're going to be listening for the first one one is the regist it's pretty easy to handle this so let's just do H do register and let's do the clients and basically we are registering a client here so client true now note that this is not concurrent in go maps are not concurrent if you want to make this production safe you could just add here a lock so I talk so in the code that I have in my description you can see this implemented with a lock so if you want to do this yourself you can check the code or you can basically just add here mutex and can lock uh here so lock and then you could unlock after I'm going to keep this simple and just wave this out so after this I'm just going to log that the client has been registered just so it can have some logs and visibility and pretty much this is the resistor part so let's also handle the end resistor which is pretty similar to the registers so here let's listen from this okay so instead of um yeah here instead of adding let's check if first it exists because there is a chance it might not exist and if it's okay then we need to do some things which is theit from the h clients this clients but first we should close the connection from the clients. send Channel okay then let let's handle the rest the last Channel which is the broadcast okay so here we have the message from the broadcast and what I want to do here is just um I didn't Define the messages so let's go back to the hub here and Define messages messages can be just a slice of message and then let's go messages depend MSG there we go and finally we need to broadcast this so let's Loop over all of the clients that are connected and do a client do send and here this is where we're going to send the HDMX to the client if you were building for Json you would send Json here so let me just create a method called get message template and I'm going to be passing the templates the message to the template default we just close the client. send pretty important as well so I don't have any elix and we just might as well just delete it because the client connection dies we are going to be waiting waiting waiting so we might as well just consider him out of the uh the connected points here what we need to return is a bite slice I think where is it the string it's a b slice so let's send the message which is going to be a pointer to message now this is where we need to create a new message template so as we saw we have this index and inside here on the shut room what's going to happen is you're going to be sending an HTML template which a message in it so it's going to be for example if you're familiar rir going to send the message comp component right this is similar to what we're going to be building so let's go here and create a message. HTML and the message tml could just be div with attributes so if you're familiar with go templates we can pass in variables for example we know that we have the text attributes there and that we also have the client ID so these are coming from the message so yeah we know that we can access these two variables from that message so let's go ahead and Define some HTML Sensers for this so this is what I've just come up with um basically the message has the same IDE as the chat room which is going to be added inside of the chat room with this swap so if you get back to the HDMX documentation you can have a look on how we can um swap them and what's the properties so here I have a before end so it's going to be added at the last element of the the list inside of it and here I just have a client ID and a text with some styling right so back here at the get message template function there is a couple things that we need to do first is get the template and paret sends the variable message into it and then convert that template into a by slice because that's what the channel is going to be receiving let's get a template this is pretty simpler pretty simple if you have worked with templates from go before PR files for me it's inside template SI message HTML we can just handle the error we can just handle the error like this and then we need to render the template with the message has the data so I'm just going to have RS message it's going to be b. buffer and let's do a template execute so now that we have the template we can execute it and pass in the rendered message and the variables that are going to be replaced again I'm going to handle it pretty simply template passing failed and at the end we just return the render message do bytes okay so this needs to be imported like so this is working awesome now we can save and we don't have the error so you can see here that we're sending to the channel the templates that has been passed has bites right okay so we can finally test this let's just make sure that we are running this go routine the Run and that here we have the client resists I also added this unregistered log so we can see the client being unresisted and then in a client we have this go routine and the right pump and the read pump as well okay let's try to make run so no errors I me go to Local Host 3000 and here we have the UI everything is working we have a log of acant has been registered with ID so let me just refresh the page and we see that he got unregistered and registered again with a new ID so it interes a new client we have anything in the response and what happens if we send a message so hello world so a message has been sent let me see if I can increase the font of the console so you guys can see so the message has been sent with text and then headers these are the headers I said that we can ignore and then the server responded with HTML right so and then HTM x what it did is it went to this shat room and it just added this message component inside here so dynamically swapping in real time the message that come from the server so this is working let's try to have a new client because the sh is no fun if you're not starting with anything and a problem I see is that we don't have messages we don't have the history of messages that has been sent before so if I send here a message I see that it has been sent here so if this is the new client you can see it's different uh but we don't have a is message how can we fix that let's try to fix that it's a pretty simple solution let's go back to the run and when the client has been registered you might consider looping over all of the messages and then sending him a message so let's do F out send and just go and get message templates message there we go so let's see if this works I think it will uh it doesn't compile because it doesn't exist here I have one client and then I have one here that I'm going to open in bits and me just create some messages some history okay so I'm going to open this client here on again and we can see that it loads with the history right so if I refresh this any amount of times you can see that we are sending the history now if you're going to production you could probably implement here instead of flooping all over the messages because this is going to be expensive really quick could have for example send 40 messages 50 m messages whatever you want to send but um don't consider sending sending everything at least not paginated so guys this is it if you understand what I teach you in this video about websockets and little bit about htx you can basically build anything um in go with with the sockets right so if you like this video consider leaving a thumbs up and maybe subscribing and see you in the next one byebye
Info
Channel: Tiago
Views: 4,149
Rating: undefined out of 5
Keywords: golang, htmx, websockets, realtime chat
Id: NPq3d2HkxWU
Channel Id: undefined
Length: 33min 53sec (2033 seconds)
Published: Thu Feb 08 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.