Check out a free preview of the full Testing and Modular Front-End course

The "Using WebSockets" Lesson is part of the full, Testing and Modular Front-End course featured in this preview video. Here's what you'd learn in this lesson:

James demonstrates how to use WebSockets to create an old school page hit counter.


Transcript from the "Using WebSockets" Lesson

>> Okay, so another way that we can extend this example is maybe we'll have a server and the server is gonna have a WebSocket connection. And then we're gonna have like, remember in the 90s, when all of the webpages had the really cool like, visitor counter at the bottom.

So we'll make one of those. That'll be fun. Okay, so to do that, we can just put together a quick little server. So I'm gonna require a package called WebSocket stream to do that, we'll also require the HTTP module and set up a little server. So we'll create server req, res, our server isn't really gonna need to do anything special aside from web sockets, aside from serving up some static assets, so I'm gonna use a package called ecstatic to do that.

You can pass it a directory and it will just serve up the briefing in that directory, pretty straightforward. You also might do (_dirname + ' /public') that way you can run the script from any location. So we need to listen on the port. And then we're just gonna let ecstatic handle the rest.

We could have custom routes in here either with a router or just checking some properties with some kind of if else statement, but this should do the trick. Okay, so now to set up that web socket connection, what we're gonna do is, so you do wsock.createServer. You pass in your http server.

And then you pass in a function. Inside of this function, you have a thing called a duplex stream. I'm not gonna get a whole lot into that, cuz that's its own quite lengthy workshop in itself. But you can do, for example, this is how we're gonna communicate with the other end.

So it's a bidirectional communication system, but we're only gonna push data at the clients. Because guess books are only a single way. So we need a variable that's gonna be the count since is the number of times people have visited our webpage. And the first time somebody logs onto the page, we should send the current count down the wire.

So we can do streamed.right, count, and then maybe plus a new one. And every time somebody visits, this WebSocket connection will be loaded. So we can also use that to increment the count. Right, because every time someone loads the page, their browser will establish a WebSocket connection. So that means that we can increment the counter.

But the problem is, every time someone visits the page we need to send that information out to everyone so we can see the counter update live. Which will be fun. So to do that we need to keep track of all of the clients streams. So I'll make an array to track that called streams, and we can push the new stream into the streams array.

But then every time we get a new user, instead of just writing that to the current stream, we actually need to write that to all of the streams. So I'm gonna do streams.forEach. I'll just do s. And then we can write that message out to everybody who's connected.

Then finally, we need to have a way of removing stream from our array when someone disconnects. So there's a package I'm gonna use for that called end of stream, that's really handy for that sort of stuff cuz there's a bunch of end of streams, node js streams API, there's kind of a lot of complexity for that.

So end of stream and then on end of the stream, we can just calculate the index into that streams array. We could also use an object with IDs but this is fine for, I think what we need index of the current stream and then we can call splice to get rid of it.

All right, so how this works is we're gonna have a counter that's gonna be initialized at zero. We're not gonna save it out to disk or anything. So every time we restart the server, our counter will restart. That's fine for now. We have a websocket connection. So anytime a client connects this callback fires.

And inside of that function, we have access to the stream and we're gonna be pushing data down that connection. So the first thing that we do, is we add that duplex stream to an array called streams. We increment our counter, and then we push out the new counts to everyone who's still connected.

And then to clean up WebSockets that are no longer connected, we can do this piece of code right here. So now if I run our server, it's listening on port 5000. Because we are now providing the server we're gonna need to run some commands separately. So we can go into here, so instead of running budo, we can run another one called watchify on our front end code, I think it's called yo.js, yep.

And we can tell it to write out to public/bundle.js. And some helpful debugging flags, might also have a script called builds and that'll build something that's more suitable for production. So, maybe we wanna pipe it through a minifier and pipe that through gzip and then save the result out to bundle.js.gz, for example, so it's just something we could do to run that one we would do npm run build, to run the development version now, we would just do npm run dev.

We still have to run the server separately from this. So I'll do npm run dev now. So now every time we edit our file yo.js or any of the files that it depends on then our bundle code will be updated automatically. So we get all the code that we're gonna need in the front end.

Okay, so the final piece is we need a bit of html. This is what I usually do for that. You may have already seen this a few times. You can have more if you like but the system is gonna take care of it. I'll cover in a little bit how we can actually generate HTML on the server side to kind of upgrade an existing page of content to something that's gonna be dynamic.

This is sometimes called universal rendering or isomorphic. People use that word to describe this kinda thing sometimes. Okay, so the final thing we need to do is update the front end code. So that we're gonna be listening for events that come down that WebSocket pipe. There's a lot of ways to do this.

Here's one of those ways. It's maybe not the best way for a real site because we're gonna be using the stream API in the browser, which is kind of big, but whatever. It's pretty easy. Whoops, should be WebSocket stream, we can get a handle to the WebSocket connection.

How that looks something like this, where you do ws:// in the URL. It just so happens that this string right here local host 5000 is the same as doing because we're in the browser after all. And we need to take in every piece from the stream and update the state accordingly.

So I'm just gonna pipe into a couple of different modules to split input on new lines and then to sort of consume the stream, we can see much more about all of how to do that kinda thing on the stream sock that I gave recently. Okay, so here we're gonna get every element in this function.

So we're gonna get rows back and these rows are actually, it's not an object stream, they are lines. So we're gonna convert those buffers to strings and then turn them back into numbers. So because we're writing, if you remember from our server code, we're printing out a number with a new line.

So, we can convert that number from a string back into a number and we can maybe emit an event. So the event is gonna be called increment-n, so I guess instead of Instead of using n as with the set interval thing, that'll be how we're gonna count the number of visitors.

So, I could even rename it, so that it's not called n anymore, it's called visitors. Okay, so I think that that's every place, except for right here. So now there's gonna be the visitor count in the h1 tag. Finally, what we need to do is update our reducer so that instead of n it's called visitors.

Okay, so maybe this will work. So, whoops, I didn't have a module installed. The problem with making stuff up on the fly is you use all these modules that you didn't expect to be using. Whoops. So now I'm gonna run the server. And I guess I already have something running on port 5000.

All right, let me kill that. Okay, here we go. The server is running. The browser looks good. So if I refresh it, whoops, right, we're no longer running on the budo port we're running on port 5000, ws is not defined, let's just fix that bug really quick. So it's on yo.js, line five, right?

That should be called wsock, not ws. So I save it and then watchify is gonna automatically recompile which it did right here. I think so. Let's just make sure if I save it again, yeah, so it's recompiling automatically. So this is another trick. It's not actually reloading the page, because Chrome is very aggressively caching.

So I'm gonna Ctrl+Shift+R. There we go. So I was doing everything fine, but Chrome was being difficult, so that's really good. We see one, that visitor count started out at something else I'm gonna now update. I'm gonna load the page in another window. And hey, we see 2 now, we see one that's kind of a silly bug, but right, so everybody updates, I guess I'm emitting an increment event, not a set event.

So I need to go into the reducer now and actually fix this because I only see the updated version. When you think it's only gonna ever increment it's not gonna be set to a value. So why don't we go ahead and fix that. So that's gonna be something that happens in the reducer.

All right, increment visitors doesn't even accept any values. So shouldn't be called increment visitors at all. It should be called set visitors. And then we should just set it to the number that we emit. Okay. It's going back into the source code. Increments should be set, that can remain the same.

Okay. Now if I reload with Ctrl+Shift+R, I think you might have to have the debugger console open to do that. Okay, and then open the debugger console there. Control+Shift+R. Okay, cool. Now I think everything is running the latest version. Just about close enough. That's weird. Anyways, there's some sort of silly little bug in here but it basically works.

Get our existing code that like cycles through the numbers still works correctly. And we've got some separation between our reducer logic and our events, our events that come from a WebSocket connection and our events that are sort of internally generated. So.
>> Do you wanna take like five minutes and like sort out the bug and then push the code?

>> Sure. Yeah, so I'll push the code right now and all of you can poke around with it and maybe you can find the bug before I do.
>> Is there some way you would start writing tests for what you just wrote?
>> Some way that you could start out writing tests.

>> What is the preferred place you start writing tests?
>> So what I would usually do is, if you're going to write tests, probably the best way to do it in this sort of application is start with the parts that are relatively easy to test, like I would say the reducer is probably the easiest part to start testing if you're gonna do that.

>> Did I not? Thought I did. Got a lot of extra false.
>> Nevermind, [INAUDIBLE] get something changed.
>> Okay. Yeah. So yeah, I would probably start here cuz it just takes an event emitter and an object is inputs. So you don't really have to do anything fancy.

You could even test this completely in node because there's no dom specific code in here. You don't even have to test it in a browser necessarily. For other pieces, you probably have to refactor them a bit more to test. Cuz there's events and there's rendering, so the rendering would have to be split out into its own piece probably, before you could really have a good shot at testing it.

So just the kinds of things that you need to refactor. Okay, let's see what's wrong with this code now. I see what it is. Okay, this is very simple. I forgot to update the state by emitting an update event. So it was just using the last value. So see if I update that code now shows to be running, and make sure that it's reloaded.

I don't know why that is. Okay, nevermind, it still has this bug.
>> Well, the initial state is zero, right? So don't you have to admit? The initial state is zero but then when you connect to the WebSocket you should get a message from the server right away that has the most up to date version of the account.

>> That's not somehow, for some reason, not firing.
>> Yeah, it's kinda a little bit odd cuz it's set and then we get vacation right away. I could print out what we're getting back from the socket. That might help.
>> See it's not firing right away.
>> Well, I'm also not seeing that message which is odd, you could say caching problem, there we go.

It could have just been a caching problem all along again. Okay, so. Okay, so that time it didn't work. Let's see what's up with it. Right, so we haven't gotten any data back from the server. It looks like sometimes it's not getting the first little chunk, maybe the WebSocket connection is buffering it for some reason.

Or it could be, there's like some connection limit we're hitting in the browser is also possible. It seems to work pretty reliably. No, there we go. I'll just print out the number of connections that we have. Just to make sure that all three of those are actually connected.

Okay, zero. Actually put that right there. Okay, one connection, two connections. Three connections. Four, well now it's working. Five.
>> It could have been just a-
>> There we go, when I reloaded.
>> Yeah, that one's not connecting for some reason.
>> Yeah, this looks like more of like a browser quirk.

Like Chrome isn't reestablishing the connection. But, I think we get the basic idea of how it works. But anyways, if you wanna poke around, let me push that one fix. Get rid of that debugging code though. Emit update event. Okay, so that code is in there.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now