How to Clean Your Sockets
Cleaning up and simplifying your sockets in socket.io using routes and controllers
For those of you that thought that this was going to be about cleaning physical sockets for plugging things into, I’m sorry to inform you that this is not the case. Instead, I’m going to tell you about how I cleaned up sockets that I wrote with socket.io, a very popular library for developing real-time socket connections between client and server machines. Before we venture in to explore what I’ve done, let’s take a look at the way routes are generally written with express, another popular framework used for writing powerful http servers.
Routes & Controllers
If you’re a web developer, you’re probably very fond of MVC architecture and how we’ve mastered its use in web application development. For those of you that aren’t familiar, MVC stands for Model-View-Controller, and architecture in this context refers to the structure of our application.
- Model — The part of the application that deals with the management of application specific data (both long and short term).
- View — The part of the application that the user interacts with and can visibly see, allowing the user to navigate and interact with our service.
- Controller — The part of the application that the View communicates with as a middle-man for speaking to the Model, mediating the interactions between the user and our database.
As you can see, this system describes three different categories into which we can classify our code. However, we can break down controllers even further for more a more granular definition of each of these components.
- Controller — The part of the controller that actually deals with the logic of our application.
- Route — The part of the controller that maps our logic to the route the user is trying to interact with.
A lot of this is highly abstract when presented as just ideas, so let’s look at a part of my application that demonstrates this breakdown:
Here, we have our router setup to map our routes to our controllers.
In the previous two sections of code, we have two controllers that are used for handling the retrieval of 50 messages and a single message from our server.
As you can see, this level of granularity allows us to create applications that are much more modular and truly isolate the functionality of components to exactly what they’re supposed to be doing. However, I had other pieces of the codebase that weren’t utilizing this type of convention when the convention was what I was missing all along.
Sockets & Socket Handlers
To brush up before I dive into my code, socket.io is a library that provides network sockets that allow us to facilitate a real-time connection between the client and the server. Let’s leave the abstract right now with my original socket implementation as an example and let’s try to figure out what could be improved.
Aside from the large groupings and all of the console logs located within the code, what problems can you identify within this section of code? If we’re following the Controller breakdown paradigm that we defined earlier—which we should be, because as a programmer we strive for consistency—we do not explicitly address the sockets as routes and controllers within this section of code.
Let’s look at one of these sockets in isolation:
We have this socket object named socket, and we tell it that on a disconnect event, we want it to execute the code that we’ve specified. This is almost exactly like how our express controllers are defined and work.
We provide the socket object an endpoint that is to be mapped to a function that will act when a client is trying to interact with it. With all of this in mind, how can we actually break down our sockets to be more readable while maintaining simplicity?
Let’s create some steps for how we should do it:
- Create another file that will house our “controls/controllers.”
- Break up the routes and controllers into their respective components.
- Smartly pass any dependencies each controller will need when we map routes.
What do these steps look like? Well, let’s see!
As you can see, we now get two new files that are separated by what they’re concerned with. This not only allows us to have better readability, but now the functions that handle the routes are only allowed to access data that they’ve been passed through where we define our routes, giving us more overall control over our application and a better understanding of what each individual component should be doing.
While we live in a world that is always trying to push forward and advance as much as possible, sometimes it is best that we take a step back and evaluate our past steps to learn and improve our future ones. My original implementation of these sockets was pretty sloppy, but had I not come back to them I would never have gained the opportunity to improve my code and discover a new way to think about the way I write sockets for future implementations.