Nuxt Socket.IO: How to Create a Stealth-mode Chat Application in Under 10 minutes
Tags: nuxt, vue, javascript, socketio
Published: 2019-12-29 06:22:53

TL;DR - In the last post in this series, a new namespace configuration feature was presented. A special syntax was presented with the feature, and this article shows how to configure the module to create a simple and anonymous chat application in less than 10 minutes. The goal of this article is to illustrate how much can be done in the `nuxt.config`, and how much less code would be needed at the page- and component- level to accomplish the task.

Disclaimer: I am the author of the [nuxt-socket-io](https://www.npmjs.com/package/nuxt-socket-io) module.

---

## Suggested reading: 

1. [Introduction to Nuxt Socket.IO](https://medium.com/javascript-in-plain-english/introduction-to-nuxt-socket-io-b78c5322d389) - This describes the Nuxt Socket.IO and basic setup.

2. [Namespace configuration explained](https://dev.to/richardeschloss/nuxt-socket-io-how-namespaces-config-may-make-your-life-insanely-easier-23ml) - Describes the feature and syntax.

The above items are good to read, however, I will try to write this article to make the example easy to follow, regardless of previous knowledge. Familiarity with Nuxt and VueJS ecosystem should be enough to help you get through the example.

## Basic setup:

1. Clone or fork my git repo: https://github.com/richardeschloss/nuxt-socket-io
2. Install the dependencies: `npm i`
3. Run the server with `npm run dev:server`
4. Open at least two browser tabs and navigate to the chat rooms page at: https://localhost:3000/rooms and have fun! Simple chat messages should be sent back and forth between the clients. The rest of the article simply explains what is going on and how this was accomplished.

## Configuring the namespaces:

* First let's take a look at the namespaces config inside `nuxt.config.js`:
![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/4zmvc07c5mmtlilypznj.png)

1. First we have a namespace for `/rooms`. This namespace will only care about any communication at the "rooms" level. Therefore, there is an emitter which will emit an event "getRooms" and the server will respond. When it does, we set the corresponding page's data `this.rooms` to that response. If we wanted to, we could also set up an extra emitter, say "crudRoom + roomInfo --> rooms" which would send an event "crudRoom" to **cr**eate/**u**pdate/**d**elete the room specified in the `this.roomInfo` message. The response back would set `this.rooms` to the new response. If race conditions were a concern, we could simply change the name of the destination.

2. Next we, have a namespace for `/room`. There are two emitters. One emitter will send the event "joinRoom" with the message "joinMsg". On the page, `this.joinMsg` will contain information about the user joining the room. The server will handle the joining of sockets to a unique namespace, as this is how the socket.io server works (`socket.join` is done server-side). After successful join, the server responds and `this.roomInfo` will get set with that data. The second emitter is there to do the opposite of joining: "leaveRoom" and send the message "leaveMsg", which would contain the user leaving the room. In order to alert *other* clients of the join and leave events, listeners have to be set up on the clients. Here we simply specify listeners for "joinedRoom" and "leftRoom" events, and also specifying a post hook "updateUsers" to run after receiving the new information. Like in step 1, it's possible that we may want to register even more emitters, for perhaps editing the room info, or notifying existing users about other room-wide events.

3. Lastly, we have a namespace for `/channel`. What is channel? Well, it's really just like "/room" but a room inside of a room. The only difference here is we treat the channel as the namespace to allow for message sending and receiving. Therefore, there is an emitter "sendMsg" which will send the "sendMsg" event with the user's message `this.userMsg`. The server will echo the user's message back (to acknowledge receipt), and after the user receive's the message, the page data `this.msgRxd` will get set and the post hook `appendChats` will append the chat to that user's copy of the chat history. For *others* in the room to see the message, they need to listen for the "chatMessage" event and do exactly the same thing `appendChats` after receiving the message. Note: in my example, I don't actually use `this.msgRxd`, but I am still choosing to have it there in case I plan to use it.

## Page structure

Here is how the pages are structured in the pages folder:

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/5pa6p957kisp6kithni3.png)

If you're new to Nuxt, here's the 30 second crash course on automatic route generation (and why 24.5k+ people love Nuxt): Nuxt will automatically create nested routes based on how folders and files are structured in the "pages" folder. If the pages folder contains *both* a vue file and folder of the same name, then the files in the folder will be treated as children of the parent vue file. The parent vue file just needs to remember to include a `<nuxt-child></nuxt-child>` in the template so the child pages get placed where the `<nuxt-child></nuxt-child>` element is. Furthermore, the underscore has a special reserved meaning in NuxtJS. It's used to indicate a parameter-based route. This is exactly what we want. When a child page wants to get the value of that route parameter, it does it by looking in `$route.params[childPage]`. Therefore, "_room.vue" would look at `$route.params.room` and "_channel.vue" would look at `$route.params.channel`. 

## Rooms Page

Here are the key parts of the rooms page, which will only care about the "rooms" namespace:

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/2sl7szpiddb2njq7361j.png)

The main requirements are instantiating the client, specifying the namespace for the client to use, and defining properties that will expect to receive data. In this case it is `this.rooms`. What you'll notice is there is *no need* to define the `getRooms` method. In fact, doing so may break the plugin! You *already* specified it once in the nuxt config, and that's all to be done. The plugin will take care of the rest!

## Room Page

Here are the key parts of the room page:

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/3393u3lmwhhvs7ppypsz.png)

Here, like before, simply define the props that were entered in `nuxt.config` and when it is desired to use the emitter methods, just *use them*. The post hook "updateUsers" is the only method we need to define. 

Now, I think I know what most readers will think. If the plugin can create the emitter methods, can't it also just automatically create the props to save the user one more lengthy step? Well, the answer is yes with a major caveat. For the plugin to absorb that responsibility, it would have to enforce and assume a datatype for every property, most likely an object. While it is my personal style to encapsulate all IO messages in *objects*, adhering to the format `{ err: ..., data: ..., meta: ...}` all users may not want to be forced to do that. And, since I can't possibly know the requirements of all projects, I could end up alienating a large user base by enforcing that. Some users may want to send simpler data types (numbers, strings) or objects of a different format. Plus, this way, the developers also have control over the initial values for their properties.

## Channel Page

Lastly, here are the key parts of the channel page:

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/8kppsvgzugffqvjs51ef.png)

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/8k48yctuk948ekpic3ih.png)

This looks almost exactly the same as the room page! In fact, maybe with more effort, I could have reused even more code between the two pages! The only real functional difference is that it is on the channel page where we allow messages to be sent and received. 

The user's `inputMsg` is encapsulated in the page's `this.userMsg` object, which will also contain the user's name when the "sendMsg" event is sent. This is primarily for illustrative purposes, but it should be noted, that one interesting thing about *socket.io* is that each socket gets a unique ID (both the client and server will be aware of the `socket.id` at initial connection). It may be more appropriate to send the socket.id instead of the user name with each event. The server could maintain an id-to-user map in this case. 

## Bonus (did you notice the extra goodies?)

1. On the server-side, as a tribute to the way Nuxt does things with routes, my IO module on the back-end automatically registers namespaces based on the folder structure. Files in the "namespaces" folder will automatically accept connections to namespace matching `/[filename]`. This should make it easier to write the server-side logic. Simply make the methods mirror the front-end methods.

2. Page-level tests to make testing quicker than manual testing in the browser. If you haven't experienced `vue-test-utils`, you may learn to love it. 

3. CSS-grid on the front end. So, if you were hoping to learn it, you can learn from these examples (just scroll down to the "style" section where it's used). Moreover, both the room.vue and channel.vue pages use it (so you can nest a CSS-grid inside another CSS-grid; originally, I thought this would break things, but apparently not)

4. Your username is not a real name, it's a pseudo random number generated based on the time you connected to the rooms page. At any time you want to change your username, you simply click the refresh page and you get a new identity. So somewhat of a "stealth" mode (but not real security, don't rely on this entirely).  

## Things to Note

* There is still plenty room for improvement in the plugin and the example. Planned for the near future may be better error handling. Currently, the developer will have to handle errors in the post-level hooks, but I have some ideas for improved solutions in future versions. The socket.io-client under the hood also provides error messages, such as "failure to connect" errors, which can probably be handled cleanly. Stay tuned.

* The example could include support for CRUD operations so that users can create, edit or delete rooms and channels.

* Certain parts of the page can probably go into components, such as each chat message in the chat history. 

## Conclusion

Ok, there you have it. In my headline, I promise "10 minutes" and, considering this was a 7 minute read, you now have 3 minutes to get it working to not make a liar out of me! :). I hope you have fun with it. No need to pay some corporation a ton of money for a chat application...you just launched one for free! And yes, I won't track your chats! Happy chatting!