🗃️U5LA3 Mini Project: Socket Server Lab
Teacher Notes
This lab has students set up a socket server for a chat room with Socket.io. The front-end and Express server are already set up. The page includes a script that sends Socket.io messages to the server automatically. The exercise involves listening for each socket message and sending the appropriate response.
Students should utilize the Starter Code (repl.it | github) to begin this project.
Prompt
This lab is about setting up a Socket server for a chat room! The Express server and client are already built, but everything relating to sockets needs to be added by you. This includes:
Initialization
Listening For Connections
Sign In
DMs
Channels
Clean Up
The app includes a script that will automatically send socket messages to your server so you can your progress. Let's get started!
Directions
All of your work for this lab takes place in index.js from the Starter Code (repl.it | github) .
Initialization
Start by importing Socket.io and the existing Express server. Recall that importing ECMAScript Modules uses the following syntax:
Follow these three steps to initialize your server:
Import the named export
Server
from"socket.io"
.Import the default export from
"./http-server.js"
into a variable calledserver
Create a new variable named
io
and set it tonew Server(server)
If you did this correctly, you should be able to run npm start
and get a notification that your server is listening. You should also have a webview showing the running chat room. You'll use this website to monitor the progress of your socket server. As you add new handlers on the server, you'll see new messages showing up on the page. Right now, you'll see any messages your currently selected user (Ashley by default) is sending but nothing else.
Note that app.js and http-server.js of the Starter Code (repl.it | github) contain the rest of the server code, which should look familiar from the Express lab.
If you've done it correctly, it should look like this:
Listening For Connections
To make your server respond to clients, it first needs to listen for client connections. This looks like this:
Add the code to listen for client connections to index.js
.
You'll write all of your socket logic inside this function. This logic is primarily event listeners with handlers that either update something on the server or send messages to clients. For example:
This code:
Initializes a vote counter
Listens for client connections
When a client connects, an event listener for "vote" is added to its socket
When this socket sends a "vote" event, the vote counter increments and the voter name that was sent with the event is broadcast to every client except the sender
These two patterns, updating state and sending messages, are powerful enough to implement the rest of the chat room.
Sign In
Your app already listens for Socket.io connections from clients, but that's a technical detail. Connections don't necessarily contain details about usernames, for example. That kind of thing is an application detail and needs to be handled on its own.
The first thing the handler should do is let everyone else know the sender has logged in. The sender already knows this, so the message should go to everyone but them. This is a really common pattern in socket communications and is called broadcasting. This code listens for any socket to send it a set price
event and then broadcast that same event to every other socket:
The first argument to .emit()
is the name of the event and the second can be any data type. That data will be sent to every handler any client has for that event.
Add an event listener to every socket for the "sign in"
event. The handler will be called with a username. Broadcast the "sign in" event to every other connected client, resending the username.
If you've done this correctly, you should see sign in messages displaying in the chat log of the website and socket messages starting to display in the socket messages log.
DMs
While it's possible for two sockets to send messages to each other directly, that's another technical detail that's separate from the application-level concept of sending a direct message. One of the easiest ways to implement direct messaging is to have every user's socket join a room based on their username. That way, anyone else will be able to send messages to the room that has their name.
Rooms are a unique Socket.io feature that allows servers to give a name to an arbitrary groups of sockets. You can think of them in relation to emitting and broadcasting like this:
Socket emitting: 1
Room emitting: Some
Socket broadcasting: All - 1
Server emitting: All
To add a socket to a room, use .join()
:
Note that broadcasting and rooms are server-only concepts. Clients are connected to the server but aren't connected to any other clients directly. All clients do is listen for and emit events.
Inside your existing handler for the "sign in"
event, add the socket to a room based on the given user.
To send a message to a room, put .to("room name")
before the .emit()
:
Create a new socket handler for the "dm"
event. The handler will be called with a message object. Send a "dm"
event to the room in the .to
property of the message, resending the entire message object with it.
If you did all of this correctly, you should see DMs starting to display in the chat log of the app and new socket messages displaying. Take a moment to examine the structure of the data in the DM objects.
Channels
Much like connecting/signing on and socket messaging/DMing are distinct technical vs. application concepts, Socket.io rooms are a technical companion to the chat room concept of channels. Clients will send a "new message"
event with details about the new room. In production servers, creating a room ordinarily involves additional server work such as adding it to a database. These kinds of operations may or may not succeed, so it makes sense to send this message to every socket--including the sender. To send a message to the entire server, use io.emit()
:
Note that entire server (io
) is emitting, not an individual socket
.
Listen for the new channel
event. The handler will be called with an object representing the channel. Emit a new channel
event to every connect client, sending the channel object with it.
If you do this correctly, the #general
channel should start showing up under the Rooms log.
Clients will also send "join"
events to the server to request to join a room.
Listen for the "join"
event. The handler will be called with an object representing the join request. Its .channel
property contains a channel name. Use the channel name to add the socket to the appropriate Socket.io room. Then, broadcast a "join"
event to every socket beside the sender, resending the entire join request.
Clients will send "chat"
events to the server to send a message to a room. Note that this is distinct from "dm"
events.
Listen for "chat"
events. The handler will be called with an object representing the chat message. Its .channel
property contains a channel name. Use the channel name to emit a `"chat" message to the appropriate Socket.io room, resending the entire message object.
If you did this correctly, you should see channel messages appear in the chat log and additional socket messages display as well.
Clean Up
The last step is to end the socket connection, freeing up resources for the server. To do this, use the .disconnect
method on the socket:
Listen for "sign out" events on every socket. Disconnect the socket in the handler.
Exemplar
You can explore the finished code (repl.it | github) as an example.
Culturally Responsive Best Practices
If the students are unfamiliar with chat rooms, show them some examples relevant to them, such as Facebook Messenger, Discord, Slack, or even chatbots from commercial websites. You may want to show them historical examples, such as AOL instant messenger, ICQ, or IRC. You may wish to compare/contrast these tools with contemporary methods of online communication.
You may wish to talk about the concept of online identities as being distinct from real identities, and how screen names have helped people from marginalized groups protect themselves while still establishing a consistent presence and engaging in a community. Conversely, discuss how people have used this kind of anonymity to hurt others without consequences. This app doesn't do anything to permanently register or protect screen names. You may want to lead a discussion on what the server would have to do to prevent someone from impersonating you, or how difficult it to tell whether the server is logging or sharing your messages with others.
Extra Help
Socket.io Quick Reference
These send a message from the server to one or more connected clients:
socket.emit("some event", "Some value")
Sender
socket.to("some room").emit("some event", "Some value")
Everyone in a room
socket.broadcast.emit("sign on", "Some value")
Everyone except sender
io.emit("some event", "Some value")
Everyone (note the io
)
These change something about the socket:
socket.join("some room")
Join a room
socket.leave("some room")
Leave a room
socket.disconnect()
Close a socket connection
Extensions
Mild
In the
"sign out"
event handler broadcast a"sign out"
message and resend the dataAdd an event handler for the
"leave"
event. This should broadcast as a"leave"
event and send the leave request data. Additionally, the socket should leave the channel stored in the.channel
property of the leave request.
Medium
Right now, the server can't tell you which channels and users already existed when you signed on. The Set
data type is useful for things like this. Sets are similar to arrays, but every item is guaranteed to be unique. If you try to add something to a Set that already exists, nothing will change. Sets are created with the new
keyword:
Create 2 empty Sets for users and channels. These should be created outside of the "connection"
event handler.
To add data to a set, call its .add()
method. To remove data, use its .delete()
method.
In the "sign in"
event handler, add the user to the users Set. In the "new channel"
event handler, add the channel name to the channels Set. Note that the channel name is in the .channel
property of the new channel request object.
Finally, let the new client know what the current rooms and users are when they sign on. Sets are similar to arrays, but not identical and need to be converted to arrays before they can be emitted. The easiest way to convert a Set to an array is to spread it into one:
In the "sign in"
event handler, emit a "joined"
event back to the original sender. Send an object with existingChannels
and existingUsers
properties. These properties should be set to the array versions of your users and channels Sets.
Spicy
Explore client/actions.js. Read through the actions
array to understand the format of the chat script. Modify the actions
array to be a chat script of your own.
Reflection Questions
Why does each socket event listener go inside the connection handler?
Why don't clients know what rooms their socket is in?
Why is at least one advantage to broadcasting vs. sending the message to the entire channel?
Why is emitting to every socket done on
io
while emitting to other groups of sockets is done onsocket
?
Last updated