Chatting with Sapper (Svelte) and Socket.IO
Tags: sapper, svelte, socketio
Published: 2019-07-24 07:57:17

---
title: Chatting with Sapper (Svelte) and Socket.IO
published: true
description: My experience creating a simple simple chat app with Svelte / Sapper and Socket.IO
tags: sapper, svelte, socket.io
---

# Context

While working on my last project, a spaced repetition learning app (play with the demo [here](https://never-forget-app.herokuapp.com)), I stumbled upon [Svelte](https://svelte.dev), a component framework that runs at build time and compiles your code into optimized, vanilla JS. I played with the tutorial a bit and it seemed really fun. I also quickly discovered [Sapper](https://sapper.svelte.dev), an application framework for building Svelte apps. So, I decided the next project I would do would be something small to help me learn about Svelte / Sapper.


In addition to Svelte, I also wanted to test out [Socket.IO](https://socket.io), a library that enables real-time, bidirectional, event-based communication between a client and server. So, I thought a good way to combine the two would be to build a simple chat application. I also felt that something like this could not only be a great way to personally learn something about these technologies but also an example that could be used to teach others (eg in a workshop, meetup, course, etc). As such I will write a little about how I found the app building process below. For the impatient, you can go directly to the app's [repo](https://github.com/tmns/chat-app) or play around with the [demo](https://chat-app-svelte.herokuapp.com).

# Setting Up

Getting up and running was rather painless and really as simple as the Sapper docs imply. With the following sequence of commands I had a new Sapper project ready to be worked on:

```
npx degit "sveltejs/sapper-template#rollup" chat-app
cd chat-app
npm install
```

The next step was to install the Socket.IO essentials. This was easy enough with `npm install --save socket.io`. However, one gotchya I encountered was that I couldn't get my `index.svelte` to properly import the `io` object from the standalone client build exposed by the server by default at `/socket.io/socket.io.js`. This could be due to a race condition in how all resources are loaded. So, I instead opted to import the package `node_modules`, but this resulted in a module not found error for `bufferutil`. 

Finally, I realized that if I wanted to go that route I also had to explicitly install the client package: `npm install --save socket.io-client`. While this is not mentioned in the getting started docs, it is mentioned in the extended docs (doh!!!). Another alternative would be to try to load from the CDN, but I didn't try this. Once I installed the client package, I was able to import it into my `index.svelte` and begin using the `io` object!

# Building out the server

The Socket.IO docs are great but they deal explicitly with Express. I like and know Express rather well, but I had never tried Polka before and since it comes with Sapper by default I thought it would be a perfect time to give it a try. Luckily, the Polka people (or person it seems?) also wrote an example (a chat application nonetheless!) specifically showcasing how to use it was Socket.IO; so, I was able to lean on it quite heavily for guidance.

Check it out [here](https://github.com/lukeed/polka/tree/master/examples/with-socketio). It's really all you need to get going. So instead of going over it redundantly here, I will simply refer to relevant server code as I explain different parts of the client below.

# Building out the client

The client simply consists of an index.svelte page and a `Heading.svelte` component. I decided to abstract out the heading to its own component in case I wanted to use it on other pages or even in other projects. To achieve something like this with Svelte is very easy; for example, my `Heading.svelte` looks like so:

```
<style>
  [...CSS...]
</style>

<script>
  export let text;
</script>

<div id="heading">{text}</div>
```

Then, in `index.svelte`, it is utilized like so:

```
<script>
  import Heading from '../components/Heading.svelte';
  
  [...]
</script>

<body>
  <div class="main">
    <Heading text={'Chat App'} />
  [...]
</body>
```

It should also be noted that you can set a default value for `text` in `Heading.svelte` by simply assigning it to a value rather than leaving it undefined.

The chat functionality itself is mainly handled by various Socket.IO `socket.on` and `socket.emit` (or `socket.broadcast.emit`) calls that are fired in response to user actions. For example, to implement message sending, we first add an event listener to the app's send `button`, which is achieved very easily with Svelte:

```
<form action="">
  <input id="m" autocomplete="off" {placeholder} bind:value={message} />
  <button on:click|preventDefault={handleSubmit}>Send</button>
</form>
```

Note also that we are binding the value of the message `input` field to the variable `message`. This will create a two-way binding and make sure whatever is typed into the field is automatically bound to our message variable defined in our `script` block (via something like the usual `let message = ''`).

When the user clicks the send `button` then, our function `handleSubmit`, also defined in our `script` block, will be called. Within it, we first add it to a `messages` array, which acts as our data structure for storing all messages of a given session and then we emit a `'message'` event:

```
messages = messages.concat(message);
socket.emit('message', message);
```

Then, on the server side, we have a listener that will listen for such events and react accordingly whenever it receives one. In our case, that simply means broadcasting the message to all users (except the user that sent it):

```
socket.on('message', function(message) {
  socket.broadcast.emit('message', message);
})
```

And finally, back in our `script` block of `index.svelte`, we have another `socket.on` listener waiting to receive such `'message'` events so it can add them to the `messages` array and display them in the chat window:

```
socket.on('message', function(message) {		
  messages = messages.concat(message);
});
```

At this point, you're probably thinking: 'okay, that's nice, but how do we display the messages to the user?'. Again, Svelte makes this very easy. With the help of Svelte's `#each` block functionality, we can simply create a list element via `ul` and then populate it with an `li` element for each `message` in `messages`:

```
<div id="chatWindow">
  <ul id="messages">
    {#each messages as message}
      <li>{message}</li>
    {/each}
  </ul>
</div>
```

Cool, now each time a new `message` is added to `messages`, the chat window is updated and displays it instantly! It would be nice for the new message to fade in though, rather than suddenly appear. Time for some CSS or extra JavaScript right? Nope, Svelte to the rescue:

```
<li transition:fade>{message}</li>
```

Yup, that's really all that's needed to add a nice fade-in transition to each message. Further, at this point, that's all you really need to have a working chat client! I could have also stopped here but I wanted to add a few little extras, one of which was a message informing the user of how many users total were currently chatting. 

This ended up being a little tricky because I wasn't sure where to store and update this value. Svelte itself implements an app-wide store; however, this proved to not be the correct solution, as I needed something that would be updated for every client every time a new user connected. The solution thus ended up being to store the value server-side, and then on every new `'connection'` event, update the value and send it back to the client, which would then update its local copy accordingly. First the server code:

```
[...]
let numUsers = 0;

io(server).on('connection', function(socket) {
  ++numUsers;
  let message = 'Server: A new user has joined the chat';
  socket.emit('user joined', { message, numUsers });
  socket.broadcast.emit('user joined', { message, numUsers });
[...]
```

Note that we must call both `socket.emit` (which sends a message back to the user who sent the event) and `socket.broadcast.emit` (which sends a message to all users except the user who sent the event). This is because we want both the user who joined and all other users to receive this event and have the correct number of users locally stored.

Then, back on the client in our `script` block, we set up our local variable to store the number of users connected and our listener for the `'user joined'` event:

```
let numUsersConnected = 0;
[...]
socket.on("user joined", function({message, numUsers}) {
  messages = messages.concat(message);
  numUsersConnected = numUsers;
  updateScroll();
});
[...]

```

And finally, down in our `html` we insert our data as such:

```
<p>There {numUsersConnected == 1 ? 'is' : 'are'} {numUsersConnected} {numUsersConnected == 1 ? 'user' : 'users'} currently chatting!</p>
```

You may have noticed in the listener above that we also make a call to a function called `updateScroll`. This is simply a function that auto-scrolls the chat window on each new message:

```
function updateScroll() {
  const chatWindow = document.getElementById('chatWindow');
  chatWindow.scrollTop = chatWindow.scrollHeight;	
}
```

The gotchya I encountered with this though was that every time this function would call, the chat window would auto-scroll, but not all the way to the bottom of the `div`. Instead, it would scroll just above the last sent message, so the user would always have to scroll a little bit more to reveal the latest message. What could be causing this weird bug??

I finally realized that this was due to a little race condition. Essentially, the updateScroll function was being called *before* the new message was actually being added to the DOM, resulting in the `scrollTop` always being set to just above the latest message! To fix this, I simply threw the `scrollTop` assignment into a `setTimeout` callback so it would be handled by the web API and be forced to wait in the `job queue` until all other code had been executed:

```
function updateScroll() {
  const chatWindow = document.getElementById('chatWindow');
  setTimeout(() => {
    chatWindow.scrollTop = chatWindow.scrollHeight;	
  }, 0);
}
```

And with that the scroll began working perfectly!

A final gotchya you may encounter when working with Svelte is that if you attempt to add an event listener to the `window` object you will get a `ReferenceError: window is not defined` upon execution. This is also due to a race condition; however, Svelte documents this and provides a simple way to interact with the `window` object via a special `svelte:window` element. In this app, the element was utilized to call a function whenever the window unloaded:


```
<svelte:window on:unload={emitUserDisconnect}

```

And just like that you not only have access to the `window` object but can also easily set up event listeners!

# Conclusion

This was a really quick and fun starter project to get familiar with Svelte / Sapper and Socket.IO. I think it would make for a great project to implement together with others in an afternoon knowledge-sharing session (eg a meetup). While there are definitely a few gotchyas, once you truly understand the problem you typically find that they are actually already documented and a clean solution exists. I suggest you give these technologies a try; both make developing rather painless and perhaps more importantly- very fun!