How to use WebSockets with React and Node

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
if you want to build real-time updates into your react application like chat inapp notifications live graphs feeds or Maps or even multiplayer collaboration features like online presentence and realtime curses websockets are your best friend in this video you're going to learn all about websockets as we build out a smooth live cursor experience using react on the front end and node on the back end here the idea is to show you the fundamentals and some react websocket best practice itic is you might be building a life cursor application but I want the lessons in this video to apply to various projects no matter what shape they might take we'll jump into things in just a second where we're going to solve some really interesting challenges around how to manage the websocket connection but also how to efficiently deliver frequent real-time updates like live curses moving on the screen but first let's take a quick look at the fundamentals it might sound a little bit counterintuitive but to understand websockets really well is going to help to take a quick look at HTTP hi I'm Alex Booker with ay the real-time experience infrastructure API let's get into [Music] it okay so HTTP we know it we use it we love it it's the foundation of the modern web and it's the protocol we most commonly reach for when we think about sending or receiving data between a friendsand clients and a back-end server with HTTP the client sends a request to the server and when the server sends a response we call this the request response model and it works really well when you know you want to fetch or send data based on a specific client event so for example if you have a feed and a load more button at the bottom when the user clicks that that's a client events and you can then send off the request nothing wrong with HTTP I'll be using it in my next project but where this request response model kind of falls down is in situations where the the client doesn't yet know when there's going to be an update available from the server for example say You're Building in app notifications the client will have no way of knowing when there's a new notification available on the server and therefore it has no timely trigger to send off a request for that information you have to remember that with HTTP and this request response model the server has no way of initiating a data transfer to the clients it just has to sit there and wait still spoken to you could say that this is an example of a oneway or unidirectional type of communication but what we need often for real-time features is two-way or bidirectional communication we need a way for the server to be able to push fresh data as soon as it's available instead of just idling and waiting until spoken to this is exactly where websockets come into play with websockets a two-way connection is established allowing either the client or the server to send fresh data as soon as it's available it could be the client sending data based on a client event or it could be the server saying hey I've got a new chat message for you or a new inapp notification or a new data point to render on the graph websockets do have a kind of gnarly Speck and we ourselves are ay have written a whole book on the subject but you know what for the purposes of this video there's just three things you need to know number one under the hood websockets establish a long lived connection this is very different from HTTP where an ephemeral connection is opened for the request and response Pair by opening a long lived connection in this way websockets can reduce the overhead of having to navigate opening and closing a connection every time you want to do a data transfer making them really appropriate for high frequency updates number two once the connection is established either side can send fresh data at will it can be binary data or it can be text-based data like Json and the really cool thing about websockets is that they are full duplex which means the server and the client can both send information to each other don't know what this is uh simultaneously uh improve proving the fruit parts of information finally I want you to understand that websockets are a totally separate protocol from HTTP but they are not mutually exclusive in fact it's very common and encouraged to use both in the same application it's not a case of picking either or it's more about picking the right tool for the job so I don't want you to think that you have to use websockets for everything in your application you will probably still use things like HTTP for authentication and typical sort of post requests and things like that but then you'll bring in websocket is the right tool for the job when you want to send real-time updates go going back to that example with inapp notifications well once the web connection is established the server can just send over that update whenever it's ready in a very efficient way the inab notification example is actually kind of interesting because it's an example where the server is more chatty than the client in this case the client is kind of waiting for an update from the server but websockets are especially fantastic and uniquely suited for situations where both the client and the server pretty chatty it could be a chat application but to bring this a little bit closer to what we're going to be building step by step today it's also really perfect for something like live cursors where we are going to be sending our cursor position every time the mouse moves and at the same time the server is going to be broadcasting everybody else's Mouse position as soon as it knows about them as soon as it gets the latest updates and because websockets are full duplex all this information is going to flow across the channel in a very efficient way next up let's bring that Vision to life we're going to build this smooth live cursor experience and we're going to give some very careful consideration to the performance implications I'm going to switch angle share my screen and relax feel free to follow along and type along we'll be doing this step by step but if you prefer you can check out the code on GitHub or read the written version of this tutorial over on the a Blog and then just watch the video to see it evolv step by step basically first up let's take a look at what we will be building the user is going to be able to log in and watch how when I move my cursor there's a red cursor rendered beneath if I open another window and log in we can see the cursor location is visible to everyone creating a visual cue about where the other users are and what they're doing it's like what you might find in a tool like figma Meo or framer and it's pretty cool Additionally you can see a crude list of Who's online I included this because once the foundation was in place it was so easy I thought why not but obviously you'd want to make it look pretty as a very quick side note at a we have a product called spaces that helps you enable collaborative environments in your own apps including cursors this tutorial doesn't use a it's entirely raw websockets but because there are these live cursors you can actually check out the interactive demo and borrow the CSS it's all open source and you're welcome to do so and the same with Avatar stack so check that out if you want to make them look pretty and who knows maybe you'll check out spaces and realize it's pretty perfect for your production project here though we're using raw websockets and we're doing curses not because it has anything to do with a but because they're pretty perfect way to demonstrate and teach how to use websockets because they are bidirectional and live curses in particular are quite demanding because of how much information is flowing back and forth over the same channel you can see that the curse is slightly animated this delay isn't so much a lag and it doesn't look jumpy this is something you're going to learn how to implement later in the tutorial we'll be building this project in two steps one step for the server and one step for the client and we'll do it in that order because the client can't really do much until it has a server to connect to let's start building up the server step by step our serers side websockets adventure begins in an empty terminal where we're going to make a new directory for our project and call it live cursor app inside this directory we're going to make another called server and navigate into it later on we'll create a separate folder for the client to keep things nice and organized ized making sure you're inside This Server subfolder run npm and nits with the Y option that just says yes to all of the options saving us some time and let's also install the websocket node module and another module called uuid which stands for universally unique identifier node does not have a built-in websocket module so we import the closest thing to an official one we have which is Ws that's been around for a long time now as well as this kind of helper module which can be used to gener Ates unique identifiers for example it will look something like this but with numbers and different letters uh in order to help us uniquely identify a connection we'll get into that soon next we're going to create a file called index.js but we're going to do this outside of the terminal let's do it in vs code and just with a little bit of foresight we're going to navigate back up into the roots of the project folder and open that in vs code because later when we create the client folder adjacent to server we'll be able to see it and work all from the same place so inside of server where we now have our package Json file and the relevant mpm modules we're going to create a new file called index.js this is going to be the primary script and entry point for our server application okay I promis we would do this step by step so here we go step one let's create the websocket server now bear with me here because the first step to create a websocket server in node is to create a HTTP server the reason why we do this and it's pretty handy we've written so much about uh websockets on the a Blog before because we have lots of helpful diagrams to help illustrate this it's just that the way that a websocket connection is established is by performing a handshake over HTTP so our server must need a way to speak HTTP hence we create the HTTP server first I will link that post in the description if you want to learn more next however we can create a websocket server by importing the Ws module and we pass to this Constructor an options property where one of the options is server uh the HTTP server but we can shorten this by using es6 and tax like so let's make this file a little bit bigger nice so at this point we've instantiated both the HTTP server and the websocket server but we're not yet actively listening for connections the way we do this actually is by just listening on the HTTP server and to this function we pass the ports we want to listen to as well as a callback function to run once the server is listening here we're going to log a success message and we'll say something like websocket server is running on Port 8000 but to reduce a magic variable we're going to put a constant for the port number and reference it like this and we'll change the string to a template literal string and then if we go to the terminal just there's a little sanity check here and we CD into the server and we run node index.js this should technically start the server and we'll see that it's running a p 8000 there we go step one complete okay step two let's accept incoming connections from users identify the user by their name you'll remember during the demo we asked them for their name and then let's also keep track of those users and their underlying connection that's going to be really handy to reference later on it will become clearer why as the tutorial progresses you might have noticed that despite the fact that we were running the server the Ws server variable is not getting much action let's change that by calling WS server do on and we'll say on a connection this is a type of event when this event happens we would please like to call this callback function which passes the connection and the request as you now know and with thanks to this diagram a websocket connection does begin with a sort of HTTP request as part of the handshake and that's what this argument here represents this other argument connection is the underlying websocket connection which we'll be able to use to you know call things like send for example when we want to send message to a user at the other end of that connection we'll get into that in just a second but the first thing we want to do really is identify the user before we go any further now the way we're going to do this ultimately when we get to the client side is when we connect to the websocket server first of all when you connect to a websocket server you specify the protocol up fronts which is Ws or WSS if you're using the encrypted version very similar to http and https so let's just say we're connecting with ws and we connect to Local Host and we're running on excuse me Port 8000 so we'll connect on Port 8000 just like this the really cool thing is that even though this is technically a different protocol we can still has query parameters query parameters if you've not come across them or you don't recognize them by name it's essentially where you add a question mark at the end of the URL and then a parameter like username where I can say Alex and you can by the way add a bunch of these by using the Ampersand and you can look into that separately in this case we just need to know the username so to summarize when we connect from the client we'll be connecting using a string like this and we'll be passing the username entered into the text input via the query parameter we now need a way to access and extract that on the server the way we do this is by first importing the URL module this comes directly from node by the way so there's no need to npm install anything and then we call url. pass and then it wants to know URL string which is going to be accessible via that HTTP request argument it's just a case of passing request.url it asks us if we want to pass the query string which we do and if we pass true that gives us access to the query property on the object returned by pass and on that object is a dictionary that has a key for each query parameter in this case username however the way I like to express this in code is not by calling the do username here but by doing it like this the thing that's really fun and interesting about a tutorial like this is that generally we want to see things evolve step by step but it wouldn't ordinarily be possible to see much of the server in action until we start building out the client with react fortunately I have a way to show you and also test the code to make sure it's working before we go too far basically with something like HTTP I mean because every website uses HTTP and it's the basis for a lot of websites and apis you can just you know pass the URL into your browser see adjacent it returns whatever you might even use a command line app like curl to post data to the server you may also have come across an app like Postman which can be used for HP requests but really really useful for our purposes here is that if we open Postman which is kind of like a request inspector request debugging development type tool where you can simulate requests without your own clients if we press command n we can create a websocket connection we'll type in the URL which is going to be uh WS this is the protocol Parts the host and then the ports 88000 and we'll also Supply the username here making sure the server is running and it is running but we'll have to reload it obviously because we've made some changes let's just see if hopefully and I'm jumping around a bit here but I'm sure you can follow with it's just that we extract the username and then we log into the console So in theory when I press connect this should connect to the server and the server should accept this username query parameter and log it let's see what happens oh my gosh it worked I shouldn't be so surprised but this is fantastic to see it means that we're off to a great start and we're well on our way to completing step number two here obviously I said we want to identify the user we've done that the other thing needed to do is track both the user and the connection now to do this we could you know when you think about tracking users and keeping track of unique users we could use the username as their unique identifier but what would happen if two users with the same usern name happen to connect it's not that likely to happen and this is quite contrived but since it's so easy to follow this best practice we will and that best practice is to generate a unique identifier for each connection and use that as their individual key or way to identify them and the way we do this basically is by calling this function uid V4 which we also have to uh implement or import sorry and that comes from the uid module that we installed previously it just so happens there's different versions of the U uid function and their version this way and the way you import it is by calling require uu ID and then passing uh V4 I don't want to go too far without double checking this so I'm actually going to log both the username and The UU ID going back to the terminal and reloading it and then reopening Postman you can see that we have disconnected so we should be fine to reconnect you can see hey here's the username and again just for fun let's let's say we change this to someone else's name and reconnect you can see here that for example Khloe has connected and they have a completely different identifier you know this is uniquely generated okay with the unique identifier in place we're at a good point to start keeping track of connections and the way we're going to keep track of connections is by creating a connections dictionary and then every time a new connection is established we're going to create a new entry by using the uniquely identif uniquely generated identifier as the key just like so so every time a new connection is established we're going to store it inside of the connection dictionary the key is going to be that unique identifier and the really great thing about this now is that first of all as long as we know the unique identifier which we have our ways of referencing later uh we can pull out a connection maybe the one associated with the user to send a direct message if that was a requirement which it isn't in this case what is a requirement however is that eventually we're going to want to do something called a broadcast and a broadcast or a fan out is when you send a message to every connection to every connected user because we have this dictionary now it's not too far out of reach to enumerate that dictionary or in other words Loop over it and just call connection.end connection.end connection.end and make sure that everyone connected gets a message at the same time as keeping track of the connections we're going to want to keep track of the users now I understand this distinction might seem very subtle and nuanced and it will be hard to really understand until we see things evolved but let me try my best by explaining that this connection object that we're getting past with the connection event is a very kind of complex prototype with a bunch of functions on it a bunch of metadata and all that kind of stuff we're really just keeping track of this connection so that we can later call connection. send in this way however in addition to being able to access the connection we also want to keep track of the users behind that connection and Associate some of our our own kind of metadata with that user for example and let's just do this together and watch it and fold we're going to want to create a new user every time a connection is established and we're going to associate with that user their username now I guess what we could do is affect the mess with the Prototype a bit and start adding our own objects but this creates two problems first of all it's a little bit fragile especially in a complex app because if you start messing with the Prototype then another function somewhere else in the code might not be expecting it or might overwrite it or something like that I generally try and avoid this when possible and also by the way in a moment we're going to want to serialize which is a fancy way of saying we want to call json. stringify for the user and if we were to call something like this and Jason do stringify the connection uh we're going to get a bunch of like random metadata through the connection we don't care about whereas if down here you know we call jason. stringify with just the user we're only going to get this very focused clean tidy information without the need to trim it down or pass it on the client for example now in addition to the username we're also going to want to create a state object to associate with the user this is a really interesting Concept in real-time applications and it goes beyond just a live cursor application so I'm initiating the user with an empty State object and I'm frankly doing this as a way to illustrate what the shape of the object is going to be and we can also use it now to give a few examples because basically what we're going to do is we're going to create two properties like this one for the cursor X and one for the cursor Y and every time the user moves their cursor on the clients in the react app we're going to send an update to the server over the websocket connection with the new X and Y positions we're going to look up the user in this dictionary and we're going to update their X and Y values then every time there is an update and frankly we could do this in other ways like in a timer for instance but essentially we're going to want to periodically send a broadcast to all of the connected clients with a list of connected users and their cursor position or in other words their Associated State we could if we wanted to just make the X and Y part of the user and for this application that's pretty valid but the cool thing about thinking about state in this way and another way of thinking about it or another word for it is user presence is that we can associate iate any kind of metadata really with the user that we expect could be updated in real time for example if you're building a chat application with websockets another type of use case that websockets are really quite perfect for you might have a typing Boolean like this to say that you're typing or not typing you might also in either a chat app but also a collaborative app you might have like an online status property uh which could yes it could be something like online or Offline that might not be necessary just due to the fact the connections either there or not uh but it could be something more like out for lunch or you know in a zoom meeting for instance we can associate any information with this client or with this user sorry and the idea is that if it updates then that update should be reflected in all the clients immediately without the need for the user to reload the page now I've just thrown a lot at you I'm going to undo some of this illustrative stuff and just keep it nice and simple you know we're creating a user with their username name we can simplify this further and we're creating a state object where we can associate the X and Y position but also maybe in the future something else as well whether that's an online status and the collaboration app or maybe your appic maybe you're not building live curses maybe you're using this as the foundation for something else this is a very versatile pattern but despite my throwing quite a few things at the wall just now don't worry if it doesn't entirely make sense this at least for me is the kind of thing that only makes sense when I see it unfold and that's the exact reason we're doing the step by step to see how it plays out in a step-by-step fashion the next thing we're going to do with access to the connection is wire up some event handlers for the connection itself so we have an event handler on the websocket server that says hey every time there's a new connection we want to run this code here's a reference to the connection but now we want to say okay on that connection on a message occurring do this and on that connection if it closes do that let's that's why have a sub step by step basically when we get a message we get past the message and we're going to pass that to a function we haven't yet created called handle message and what we're going to do here is pretty clever I think because in addition to passing the message which is available as an argument here we're also going to pass the uu ID this is a way of capturing the value of the variable so that every time we get a new message we'll know who it is and the identifier of both the connection and the user that will allow us to look up those respective values if we need to and alter them again we'll implement this handle message function in just a moment but I'm choosing to do everything down here at the same time where if the connection is closed we're going to call a function called handle close and we're going to pass the unique identifier as well this will be handy so that we can for example remove or delete the entries from the connections and users dictionaries while I'm up here let's create the event handlers elves okay with the event handlers declared and wired up we can move on to implementing each respective event handler maybe we'll call handling messages step number three I I know I just mentioned this but as a quick reminder because it's so important to understand what we're doing here more or less every time the user moves the cursor in the react application the react application is going to send a message to the server with that user's X and Y coordinates with the cursor X and Y coordinates probably the message is going to take adjacent format and it's going to look something like this with an X and Y position like so what we want to do in this handle message function is take the whole message this is what the whole message is going to look like so if a client is going to send a message and it's going to look exactly like this we're going to want to take that message and look up the user and basically update that user State uh say we have access to the user we could say user. State dox equals whatever we can say user. state. y equals you know whatever like this but I'm just going to tell you right now that because this application is so focused on cursors we're probably going to be able to just do this and overwrite the user's state with the message remember the state is separate from the username so we won't be affecting the username the user. username is still going to be there what we're doing every time we get a message is the message is going to be uh the state and so the object will look like this we're going to copy it and we're going to replace the state something like this just to give you some context about what we're trying to do here again I'm taking a moment to explain it the best I can because it's quite hard to understand unless we can visualize it in some way but without the client application that isn't possible let me let me clear up all this craft and explain something else which is that the way websockets work is that even though we're wanting to send Json we're wanting to send a string which is Json like this what we get on the server a bytes so the first thing we have to do is called bytes. to string to convert the bytes to a string go figure and then we want to convert that string into an actual object and the way we do that is by calling json. pass and passing in uh those those btes what we get in turn is the actual message you know what I'm going to illustrate this right now let me print out the message like so uh go back to the terminal rerun the server applying all those updates we've made here in Postman our favorite tool of the day we're going to clear the messages and reconnect this time is Chloe so as you can see Chloe connected here's their unique identifier now here's the cool thing about Postman you can actually choose a format like Json and send a message and as it happens I've already ah I did remove it cuz I'm too eager but we'll copy more or less this exact same message and paste it in here what I'm hoping to see is that when we press send this handle message function is going to run it's going to pass the message and log it let's see what happens and just like that you can see the message printed into the console and just to illustrate that there are no smoke or mirrors you know we can update the value here and and see it updated and this is actually the closest thing we've had to a realistic client so far because imagine someone Con in fact let's just go from scratch let's say uh the madine connects right here and they've connected as a user via the react application they their cursor is probably going to start in the top left but every time they move the cursor just like this I'm pressing command enter to send the position with Postman you can see that the server is receiving the new cursor position and this is fantastic because it's happening over the websocket connection without the need to do a post request and and as you will see in a moment when we get across to the client side of things it will be possible for the client to subscribe to everybody else's cursor position received those cursor positions and rendered them some way uh on the canvas with the message now accessible to us we have everything we need to actually look up the user because we have The UU ID right here to look them up it's as simple as going constant user equals users and then we pass the key which is the unique identifier here and then we literally say user. State equals message and again as a quick reminder I think it's worth reiterating we we could do something like this message. x but because we're only expecting a very specific shape of message here we can do this in the tutorial probably definitely in another type of application you would want to maybe have like an if statement and check if the message type is equal to cursor update do this if it's equal to chat message do that but again this is contrived so we have a privilege which is that we can assume things are a certain way and work a bit more efficient L here I'm going to call that step three step four is a really really interesting one because so far we've accepted connections we've accepted incoming messages but what we haven't yet done is send a message to the clients and actually that's the whole point of websockets right for the server can push fresh information as soon as it's available there are lots of different models and patterns for sending data from a server to a client basically it could be Ono one like in a message or maybe you're showing a progress indicator to a specific user based on their action that would be sending a onetoone type of message to a very specific recipients there could be one to many for example where the server is sending a message to a group of specific users uh for example those in a group chat or those looking at a specific page like a live sports score update or a live graph for example and then there's another type of on to many interaction where you send it to every single connected client and as you now know we call that a broadcast and that's the model we're choosing to use for this type of application so I'm going to create a function called broadcast like so now don't be alarmed if you see that I'm not accepting an argument it would be pretty natural to accept some kind of message or data that we expect to broadcast but in this case the way we're going to do this the way this real-time application is going to work is that every time the server receives a message which we know is going to be a new cursor position from any connected client what we're going to do is we're going to broadcast that list of users to everyone who's interested in other words every connected user this is fantastic because it shows Who's online and their Associated States and part of that state of course is their username and their X and Y coordinates this means that the trigger for everybody receiving an update is instantaneous as soon as there's an update available we're going to broadcast the server's view of state to every client where the cursor can be updated on the screen immediately and again just to be clear the benefit of websockets here is that we're getting that fresh information without the need for the client to reload the page that's the essence it's great for live cursors but you could see this being really handy for a notification or for a chat app or something like that as well so step four implementing the broadcast function there's not too much to it really the way we're going to do this and there are a few different ways you can enumerate a dictionary in JavaScript but we're going to call object. Key this returns an array I believe of all of the keys which are going to be the unique identifiers once we have that array of keys we're going to call the for each function on that array and each uh each object is represented by the key which is the unique identifier so we'll represent it like this and here is the function and the code we're going to run for every single uh connection first of all we're going to want to pull out that specific Connection by referencing it via The UU ID like so and as I hinted to previously this allows us to call connection. send what are we sending well we're sending a message and that message is going to be the stringified version of users and we send it like so the only thing left to do to complete step four and it really is quite straightforward once you get the framework in place you can really start to move quite quickly here the idea is that every time we receive a new state update or a new X and Y coordinates we brought we update the users array uh or the users dictionary I should say and then we broadcast the users to everybody and if I was being really good here I might honestly call this something like broadcast users but this app is so simple we can infer it from Context I think listen we've written a few lines of code we haven't tested it but we've got Postman to hand to see if we can make things work so what I'm going to do is Rerun the server reopen Postman and just you know clear the messages and clean things up a little bit I'm going to connect as meline again now here's what I'm expecting I'm expecting that when I send the X and Y cursor position to the server it's going to update its internal view of States via this users dictionary and then it's going to broadcast that users's uh dictionary in Jason format to every connected client now in this case I am the only connected client so I'm expecting a bit of like an echo effect where when I send the updates the server is going to send me back what it sees so let's have a look I press send and check it out the up arrow means we uploaded or sent the X and Y coordinates and what we get back from the server looks something like this this is Madeline's unique identifier their username and their Associated State remember the idea is that as the cursor moves maybe they make a big move in the XY X position here and sends it when this message gets sent it should basically be this one it should replace the state entirely so let's minimize that press send you can see we upload what you saw in the message text input here and what we get back is that new value I love Postman I really love that you can open a new tab like this you can literally uh see all your previous URLs and reconnect just like this and now both clients are connected by the way and while it's not the best visibility in the log here you can see that Chloe's connected and previously it was meline they're both connected at this point and this is very illustrative because right now Chloe has not sent any update just imagine these were two separate web browsers both running react applications meline just moved I'm starting to get really excited I sound like an F1 commentator but if meline sent her why position like this yes we get that kind of echo effect but crucially this is really important now Khloe is receiving these updates and you can see that actually they don't have any state yet that's fair enough we should check for that and the client code is not to make a faux part but crucially we can see Madeline's most up-to-date cursor position and just for fun you remember how I was like you know pressing command enter a lot to you know keep updating what you see now when I switch back to the tab with Chloe is just a stream of updates accessible immediately available once we get around to the client application we can access all this in JavaScript it might not be so hard to then take this data to render a cursor on the canvas the one thing we haven't done and this is pretty much the final step for the client or for the server sorry is implement the handle close function fortunately it's not too hard the idea here is that when the connection closes uh we want to delete the uh user and the connection from our dictionaries respectively and and because we have access to this uu ID it's really quite simple we say cons connection equals connections and then we reference the connection we do the same with the user like this and then we call well I was going to call delete on the variables but I think it makes more sense to do it like this actually uh we just use the delete keyword to remove remove the reference to those objects it's really that simple and because we have that handy broadcast function we're going to use it why because well now every time one client disconnects the server is going to broadcast the most upto-date version of the the users's object in particular uh so that the client can kind of figure out that someone disconnected um because they won't be part of the users anymore they'll receive the most upto-date version of the data in a more like realistic production app what I would probably you to do uh is like raise an like send a specific message that says us a gone and then you know say I went and disconnected tell the client who disconnected that'll make it easier to sort of remove them from a whose's online list or you know by the way when it comes to whose's online list I just showed a very simple example where they're either there or they're not but in an ideal world and you know spaces we thought a lot about this uh you you probably want to show oh my gosh I'm getting a little bit you see here when I close one Tab and the user disconnects you might want to show that they were around recently and they were last around for a few seconds ago it wouldn't be possible to implement something like that with my crude logic here uh but if you use a product like spaces that would be easy and of course you can wire up your own events here as well the only thing I'm going to do honestly just to make this a little bit easier to follow along is I'm going to log to the console where the user disconnected I'm also going to come up here and apart from just logging the message I'm going to log something more spe specific with this in place we're in a very good place to inspect how everything's working and just double check everything's all right before we go onwards to the react client which I'm really excited to get into because a big Focus here is how to use websockets with react one of the most prevalent and nice to use friendsand UI libraries out there in my personal favorit okay I'm going to cose Stu up a little bit here and make sure I'm uh disconnected on both sides I'm going to collect as connect as mine and then disconnect as mine and it should show that madd's connected and then disconnected we'll reconnect and then when I press send update vest State we see a message log that says mine updated their state and just for a quick sanity check we'll connect here as Chloe and send a few more messages as Madeline just to make sure that we are getting the update and everything associated with it again it's going to be so nice to be able to reference this object in JavaScript on the react side and do what we need with the data all right that's it for the server let's move on to the clients we things are going to only get more interesting okay time to work on the react clients you'll remember that we made a tidy folder called server to house all of our server code let's now create a new folder called client except rather than create it from complete scratch we're actually going to use the mpm create command which allows us to scaffold applications using various modules without the need to install them globally for example we're going to use the V module and the latest version of it to create a new project inside of folder called clients we can also pass an argument like this to specify the template which is going to be react the really cool thing about this is that it will bring in all of the necessary react modules as well as some basic boiler plate that we would otherwise have to create from scratch ourselves let's follow the instructions on screen here and navigate into clients and run mpm install this will bring in react react Dom and a few other de Dev dependencies we may or may not be using we're also going to use this opportunity to pre-install all of the modules I anticipate as needing to build for react clients the first is a module called react use websockets the next is a module called Low dash. throttle and finally we're going to install a module called perfect cursors now I'm installing these up front so we can minimize the terminal and focus on the code it's great for my tutorial here but I'll explain these all in more depth as and when they come up just rest assured because they're installed we can now import them with ease just to make sure everything's working okay while inside the client directory I'm going to run npm run Dev also what's really cool about using V is that we get hot module reloading out of the box so any changes we make in the code will be updated in the application without the need to reload the page in other words we can just leave this running in the background now for a little bit while we flush things out I think in time we'll start to peel away some of this boilerplate logos and text and things but for right now I'd rather focus on creating a first component if you remember from the demo up front the first thing we ask is for the user's username so we should create a screen and a form to capture that and as a reminder we pass that username to the websocket server when we connect so it's clearly a very important step I'm going to create a new folder called components just to keep things nice and organized and inside of that a new file called login. jsx I'm going to level with you I personally always feel a bit weird in tutorials when they start p tting stuff I like to see it evolve step by step as I'm sure you do too but since this is a long video all about websockets and what we're creating here is quite a basic react form I'm not going to type it all out or belabor it step by step nevertheless we're going to want to see this login component in action so inside of app.jsx where there's actually quite a lot of CFT I'm going to start shipping away at it right now like we obviously don't want these logos in our real project we're not building a counter application either we can remove this and frankly everything inside of these parentheses after return is Superfluous so we'll get rid of that as well what we do want to do however is import the login components from components SL login and then we just kind of seeing in action here it's not quite correct but we want to just make sure it's imported properly we can then uh return the login components meaning that when we look at the browser and reload the page uh we are prompted for a username now it's kind of cool this is in the center maybe kind of but whenever there's CSS pre-applied I get the feeling like it might get in the way later so for right now I'm just going to remove app.css it looks like probably there's some Global Styles coming from index CSS as well that I don't really need so I'm going to go into index.html and I think just comment out the CSS for a second oh actually it looks like it's probably imported in the main file instead so we comment that it looks a little bit ugly right now we might refine this in a second but this is how I want it apart from scaffolding the application we could say that step one is creating and rendering the login screen I guess that means step two is to somehow log in and when you log in you're going to want to see a different screen in this case we call this the login screen when the user logged in we're going to show them a screen called home which I'll create in a new file deliberately inside of source not inside of components here I'm going to export a very simple uh component that just Returns the text home this is purely illustrative at the moment and then inside of app.jsx I'm going to create a state variable called username I'm also going to wire up here this uh this onsubmit prop to the set username function returned by use stat just to explain that quickly when we uh submit the username form this Handler is run we prevent the default browser action which is to basically reload the page as it sends off a request we don't want that because we're handling it with some asynchronous JavaScript and then we call the prop onsubmit with the username value the signature of this onsubmit function matches that of set username perfectly so we can just link them up like this now here's the thing app.jsx is the roots of our project it's the first thing that gets rendered pretty much and then it in turn returns and renders the login form unconditionally but that's not really what we want to do we only want to show the login form if the user hasn't submitted their username this is really quite simple to achieve first of all we're going to import the home components because that's what we're going to want to render if the user has submitted their username and then we'll comment out this return statement commenting it because we can still reference it but we don't need it anymore and we can apply the Turner operator like this to say hey if the username is set wow great news I want you to return the home component please if it is not set then I would like to return and we'll actually just copy it like this and then comment it actually then I want you to return the login component and by the way hopefully I'm not moving too quick here when I say that you know we actually want to pass the username to the home component because it's within the home component that we're going to actually run our websocket connection code so of course the home component needs to know who is who and we can you know simplifi not simplify sorry but we can illustrate this a little a little bit and instead of saying home we can say hello and then you know pass the username for example like this that could be pretty cool let's test it out ah we have a little error which is that home is not actually inside of the components directory so we just do it like this and now when we enter a username like Alex and hit submit it says Hello Alex and if we reload the page and say we connect as a different user for example Chloe it says hello Chloe coming back to the code believe it or not we have almost everything we need to connect to the websocket server from the home component now I think it's a good step to quickly run the websocket server so I'm going to open a new terminal window navigate into the server and I'm going to run the index.js file and run the websocket server on P 8000 this is now running in the background yeah we can connect to it from Postman but we're beyond that now we're in the react World and we want to connect from our react application how do we do that well something you should know about websockets is that they're not really specific to react at all in fact they're a built-in web browser API available in every modern web browser very widely compatible you can literally import websocket and access all these methods and functions here's a really good example of a vanilla browser API web socket in action however what we tend to do in the react World Is We use a module called react use websockets here it is on GitHub the advantage of using this module over something like the browser API is twofold first of all the API is exposed via hooks which is very idiomatic in react it makes it very easy to know where and how to import the websocket connection bear in mind with websockets you generally share one connection for all of the different types of communication in your application you might have a lot of questions like where to put that connection how to pass it around how to clean it up a fish this library takes care of all of that for us by exposing a hook called used websocket and by the way I'm not sure if it's Illustrated here well but there is an option called share and when you set share to true what happens is the used websocket hook will share the same underlying connection no matter where you call the hook so if you call the hook from different components or different instances of the same components the used websocket hook can reuse that connection instead of opening a new one and again it takes away the burden of you having to worry about how to manage this connection the second reason to use the used websocket Hook is that it comes with some extra features that you're probably going to need the thing about websockets as an API especially this browser API is there it's pretty minimal that means it's super flexible and customizable but the downside with a customizable protocol is that while it's custom there's often a lot of work to do on top for instance something very basic you definitely need is automatic reconnection logic like if the client disconnects because the user goes under a tunnel or the Wi-Fi disconnects or they're connected on cellular and the cellular provider decides to re-root the connection to a different network Master for example anything like that can happen and we need a way to reconnect this module has a built-in uh option to enable that essentially as well as some other handy convenient apis like it has an enumeration for connection States it's a mild convenience but it also has things like last Jason message and send Jason message basically allowing you to not have to think about converting things to and from Jason highly recommend this Library when you're working with react and you know what we've already installed react used websocket at the beginning of the video so to access it we just have to import it like so you might be surprised at how quickly and easily we can use this h and get connected to the websocket server inside of the component in this case called home we're going to call the use websocket hook the first option used websocket takes is a URL to the websocket server which as it happens we have typed out already here we're going to copy this last the query parameter you'll see why in a second and put that into a constant called WS short for web socket URL we can then pass that constant to the used websocket hook the second argument is an options object where we can set all kinds of options as described in the documentation one of those options which you might need in your project is share however we don't need it today the only option we need is the one called query prams and if you haven't guessed already this enables us to pass the username as a query parameter to the server so that we may extract it and identify the user in a nutshell if we log in with a name like Alex under the hood what used websocket is going to do is append a query parameter with the same name as the object in this case username and then you know the value itself right which will be whatever we input into the text form use websocket returns an object with all kinds of goodies on it you can see there's L Json message send Json message we'll use both of these in time but for right now we're doing the step by step and the first step is to actually send the cursor position to the server so here's a really key point in the tutorial where we set up all of the websocket basics we have the server running we can connect we have all our modules installed the next two tasks our final tasks can be split in two ways right one is to actually send the cursor position we'll do that first hence what we're going to do here is extract send Jason message that's all we need for right now but later we'll also want to subscribe to update so we can complete step two here which is to then obviously receive the updates and render everybody's cursor on the screen by the way if you recall we installed this module called perfect cursors that's going to come in handy a little bit later I do believe that by virtue of calling used websocket we connect to the server so if I'm not mistaken the client has kept reloading thanks to V's HMR the server is still running as well which means if I open up the uh the clients and let's just put these side by side so I can see the server in theory when I connect we should see that Alex is connected and just like that through react we've connected with the unique identifier that's has been generated headed back to the code the next thing we need to do is send the cursor position now the way we do this first of all is by calling use effect use effect essentially provides us with a way to execute some code the first time the component is rendered we we denote that by this empty array essentially we can pass the list of state variables to watch and if they change we run use effect but in this case when you pass an empty array it basically translates into something like hey whatever code you put inside this block just run it the first time the component is rendered please don't keep rerunning it and the reason why that's relevant is because here we're going to add an event listener to the mouse move events the Callback function passes an event argument like this but we can access the client X and the client y aka the mouse X and Y positions every time we get the X and Y positions we're going to want to send them to the server however there's a problem I mean the first problem is that we haven't imported use effect yet so that could be a problem so let's quickly import use effect let's also format the code a little bit while we're here the second problem is a bit more subtle not easy to spot right now but it will become an issue later on and that is the mouse move event could get fired a hell of a lot of times in not a very long period of time because if you have a high DPI or mouse sensitivity uh and you're moving the mouse around it could be firing off like thousands of Events maybe thousands every second and then also sending that data over the websocket connection like yeah actually to be honest the websocket connection on Local Host isn't going to struggle too much with that but you have to consider the fact that probably if you're going into production you're going to have like tens if not hundreds maybe thousands or tens of thousands if you have tens of thousands of users definitely use a you do not want to be dealing with the web socket infrastructure headache that follows but all of this is to say that obviously if you have multiple clients setting lots and lots of events it's going to become a choking Point really quickly uh so what we want to do essentially is not send the mouse every single time it Twitches but throttle it a little bit and if that rings a bell it might be because earlier on in the tutorial we imported a modu modle or we installed a module sorry that we will now import called Low dash. throttle this is a really clever function you can Define first of all an interval for example every 50 milliseconds and this says hey don't call this function that we're about to Define any more than every 50 milliseconds you could make it 1,000 milliseconds or 1 second and say hey don't call this function any I don't care how many times you call it I don't care how many times this event handler is run throttle the function don't let it run more than once a second that's a bit slow 50 is about The Sweet Spot actually where you get a very smooth experience but much better performance what function are we throttling well that's kind of up to us to Define by calling the throttle function and when we call the throttle function we pass two things first of all the function to throttle and then secondly the interval itself or how long to wait before allowing it to be called again and again one way you can name this variable and there's a few different conventions we could choose from but I'm just going to call it San Json message throttled and assign to it the value returned by throttle seemingly we can now call sanjon message throttled with an object to send this is the websocket message we're going to send with the X position which will be e do client X and the Y position which will be e do client y I say seemingly because there is a small problem here which is that effectively this component this function is going to run every single time the component renders when does it render uh every time something changes in the application State how often does that happen uh literally every every millisecond more or less with live cursors because they're moving all the time and what that means is that we'll be calling this throttle function essentially over and over and over again and that's a bit of a problem because it's going to affect the internal timer and it's just not going to work as expected if we want to hold on to a reference between renders and not keep record calling it that's when we use use R and so we'll import use ref we'll wrap this up in a call to use ra and by the way one thing you should know about use R is that whenever you wrap up a function like so you can't just it doesn't just return the function it actually returns an object with a meth a property called current that represents the method uh the method sorry that is throttled in this case uh so we're going to have to slightly tweak this right here let's test this out it should be visible it should be demonstrable because on the server uh we we added some logging every time the user State changes so and again there's no need to reload here CU V should have done it for me I'll reconnect as Alex and we're not reloading the server because nothing's changed if I reload as Alex and I moved the cursor can you see how it keeps updating my state it says Alex updated the state and if we open another tab here and we connect there someone else like Khloe for instance we'll see Khloe's connected and then the cursor position moves like so if I flick between tabs and you know it's kind of hard to illustrate St on the same screen with the same Mouse but you get this idea now hopefully that the server is getting all of these updates and what we can't see yet but we're about to tap into is this idea that every time the server receives an update it broadcasts everybody's cursor positions and their State uh to all the other clients we can now subscribe to this in real time and update the Page by rendering everybody's cursors well I said we couldn't see this but actually in Chrome in the developer tools if you make sure preserve log is ticked and you reload the page you'll see all the requests the page made including if we filtered to WS in other words websocket connections the connection is still open so when we click on messages it's kind of cool because when we log in now you can see ah sorry here you can see the websocket connection you can actually see the uh kind of similar to we saw in Postman right you can see us uploading the X and Y position and what we get in response is this message with a snapshot of the most upto-date State it's true that the username Alex here is connected twice that's just because of uh me opening two windows here but if I disconnect even we should see that updated essentially uh as we send out new messages I guess they're at the bottom yes so now you can see it's just me connected anyway the really cool thing is that this is here this is ready we're getting all this information without having to reload the page all that two-way connection is there and now it's just the case of accessing that in JavaScript there is just one teeny tiny issue though as we embark on this part two of the two-part problem one sending and one receiving and rendering cursor updates it's just that if we are throttling the cursor update every 50 milliseconds or so it's not a lot but it will still create a kind of noticeable jumping especially when you combine that I think with the natural latency and cl incurred when you send something over a network that's where the perfect cursors get a repository comes into play what this Library does essentially and it's very clever and I like it very much it plots the transition between two cursor coordinates allowing us to animate the cursor and smoothly blend it even though that won't be completely completely real time I mean in an Ideal World the cursor would update just as instantaneously as if it was the local computer and it would be as smooth and buty as the screen's refresh rates but just due to the Natural like effect of the latency that's not going to be possible anyway plus that delay using a library like this is going to be really really handy and what I really appreciate is how they already have a react example so what we can actually do is copy a lot of this into place so just to summarize we're going to set up this library in a matter of minutes and then we're going to hook up our setup to the actual messages coming in to render cursors so first of all we need this cursor component we're going to do this together here instead of components we'll create a new file called cursor. jsx and we'll paste the code it looks like this uses typescript so we're going to just remove the type uh annotations here next we're going to create a folder called Hooks and we're going to create a new file called use cursor. jsx then we'll copy this hook and paste it inside of the file once again I'll need to D typescript this it basically comes down to removing I think for type annotations uh like so next we're going to go back to home. jsx and do you remember how on the object returned by used websocket we could access a few different properties including last Json message this allows us to reference the most recently received Json message and it's really intuitive to use because it's very much the same as using a state VAR variable in react except every time we get a websocket message this value updates the component reenders and we can do some logic essentially what logic do we want to do well instead of saying hello username what we're going to do is say if there is a last Json message because by the way the first time the component renders there won't be not really we're going to return a div and instead of that div we're going to call a function we've not yet defined called render cursors and we're going to pass the last Json message we're going to declare render cursors at the top here because it's not really specific to the component and it's going to take essentially the last Json message but we know having looked at the shape of the message before what it represents really is a list of users who are currently connected and their reflected state so we're going to bring this argument a little bit closer to home with its name and then we're going to return object. keys on users remember because we used last Json message that actually takes care of turning that Json string into an object which in this case uh is kind of like a dictionary right where we can call object. keys and then we can map every single user that is part of that collection we'll pull out the user like so and then we're going to first of all import cursor from components SLC cursor and then we will return for each user a cursor we put a key we use a key here to satisfy react and then for the points it's actually represented as an array which will be the user will be the X the cursor X and then the cursor y like so in a little pair two pull type situation like this something else we're going to do ever so quickly before we actually run the code now is in addition to adding the event handler when we first uh render the component I'm actually going to call send Jason m message with a x and y equal to zero and the reason why I'm going to do that is because until we send a message our websocket server isn't really preconfigured to know what's going on so we're going to send this message off to just let it know that we're here and that we have a cursor very very quickly after that uh it will probably receive the genuine X and Y coordinates the second the user moves the mouse the other thing this does which is kind of Handy is again every time the server receives a message it broadcasts every body else's state so this is kind of a cheeky way of asking uh to to asking the server to send everyone's State the second we load the component okay that's maybe like 100 plus lines of code without testing anything so let's see what happens when we run the application I don't think we need to but I feel better by reloading and and there we go we've got our first Arrow so component. jsx on line four is struggling to to find used perfect cursor let's have a a quick look at that I guess because I did not call it used perfect cursor I called it use cursor and just like that it seems to have reloaded except we're missing our home screen so we probably have another error it turns out cursor. jsx does not provide a default export I suppose what I need to do in this case is reference it by a name like so all right okay moment of truth let's see we're going to connect as Alex hit submit and it says react is not defined not a problem let's go into use cursor now and we'll import react okay let's have a look um oh my gosh okay this is fun it looks as though there's an error happening on line 20 if cursor so let's go to 20 I'm not sure I'm actually not 100% sure what's happening here but I do realize that I've gone about this in the wrong way it could be it should be import start as react from react and I do just wonder if that might help it looks as though we have another little bit of typescript here I honestly can't tell you why that isn't just failing in JavaScript that does not look like valid JavaScript but I think it's possibly what was causing the confusion here and check this out you can see that as I move my cursor I'm logged in as Alex right now the cursor is kind of updated with that small delay under the scenes and if we open another window like so and we log in as uh you know let's keep logging in as Chloe for example you can see that Khloe's curse is right here rendering in smooth real time as well just for fun I've been curious what would happen if we go to home. jsx and and we Chang the throttle to like a second this could be kind of wild but also Ian it's going to look terrible I think but it's kind of interesting to illustrate so you can see that it only updates every second or so so you've got that much bigger lag um but it's clearly more performance in the sense that you're sending less data over the network but here's the thing because of perfect cursor despite the lag it doesn't look jumpy like it Blends between those two points as and when we update the cursor position the last thing want to do here is a very quick thing but it's to create a new function which I'm going to paste in this case called render users list it follows very similar logic to render cursors except instead of rendering a cursor for each User it's going to render their application state in a list to make sure this works we're going to come to the return function here and we'll call render user list and without even having to reload the page thanks to some Vite magic V magic sorry you can see the user and their states and check it out if I connect to Chloe real quick you should see Chloe connects and if we close it disconnects the idea behind this essentially is that you should be able to create an avatar stack like this again as I mentioned at the beginning we have these interactive demos where you can copy the CSS if you like but there might be a bit more work to do if you want to make it work really really nicely where like uh it it shows that the user was recently online and not just offline or online in a very in a very binary way it's also the case that because here we're just dumping the state onto the screen it works quite well but if you wanted to do something in response to like a specific user disconnecting or even detect which user disconnected you would need to do some kind of diff on the two objects here or or like when we touched on when building the server you could raise some kind of event that describes the specific event of the specific user disconnecting well there you have it folks a realtime live curses example using react in this video we got to learn learn how to set up a websocket server using the Ws node module and then on the client we used a module called use react websockets or react used websockets in order to idiomatically manage the websocket connection you can see here that this is a kind of contrived example I mean what you'd probably do in a real application by the way is hide your own cursor but render the other persons I think that would be a great next step to take and of course there's a lot you can do to make this look prettier I'm making this video in collaboration with a which is not something we've really spoken about today it's a real-time experience infrastructure because it uses websockets under the hood it's not unreasonable to think of a as hosted websockets when you use websockets there's a lot of complexity you have to deal with I mean here we're at a point where it's good for a demo and it might scale to a reasonably busy production type of application but there's lots of things you need to consider when it comes to to realtime infrastructure like how to maintain the lowest possible latency in a global application while preserving the highest degree of up time introducing redundancies yes it's true that the react module for websockets can automatically reconnect but you should ideally have a mechanism on the server to buffer undelivered messages such that they can be redelivered when the user reconnects and they have a seamless experience and this is a talk nothing of this challenge is scaling websockets I actually have a whole video about that the really thing when you use a is that you get all the benefits of websockets with none of the headache and it also has an idiomatic react hook available that makes it really easy to integrate into your react applications in general I did make reference to spaces which is a product built on top of the core a infrastructure that enables you to build collaborative environments into your application it includes features such as cursors and Avatar Stacks in addition other features you probably need like user location as well as component Lo blocking it uses a very clever mechanism to make sure that two users can't accidentally overwrite each other's work and because this all works with a it's not hard to drop down a level and for example build real-time comments or a chat alongside this collaborative type experience of course you can find the code on GitHub and what I haven't really mentioned by the way is that this video is based on my complete guide to websockets with react also created with a where in addition to more or less this exact same tutorial I shared some core logic about how websockets work as well as some best practices when using react and websockets together such as where to put the connection I'm personally a big favor of the used websocket hook but there are other kind of patterns you can adapt that might be better suited for your precise application I've been Alex Booker with a thank you so much for watching if you enjoyed this video please let me know in the comments drop a like And subscribe to the a YouTube channel where we're going to be uploading more videos about websockets and how to build really impressive engaging real-time experiences
Info
Channel: Ably Realtime
Views: 9,573
Rating: undefined out of 5
Keywords: ably, realtime, websockets, mqtt, sse
Id: 4Uwq0xB30JE
Channel Id: undefined
Length: 71min 51sec (4311 seconds)
Published: Tue Nov 14 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.