Building a Real-Time Multi User Text Editor using JavaScript!

Your Saas needs team functionality, but how do you do it?

Welcome to the dynamic world of web development, today our mission is crystal clear: fashioning a real-time synced text area that fosters seamless collaboration among users.

In this tutorial, we'll not only achieve synchronization but also implement an audit history, ensuring transparency and accountability. Let's dive into the details of our coding journey!

Download the code samples here. However, the crux code is given below.


How do we accomplish this?

In our pursuit of real-time synchronization, we'll leverage bidirectional technologies that facilitate the smooth exchange of data between clients and servers. We've got a trio of superheroes at our disposal:

  • WebSockets: This communication protocol allows data to flow bidirectionally between clients and servers in real time. It's the backbone of our synchronization strategy.

  • WebTransport: As the cutting-edge solution for web transport protocols, WebTransport provides high-performance, low-latency communication, elevating the overall user experience.

  • HTTP Polling: For those stuck in the dial-up era or facing network challenges, we'll implement HTTP polling as a fallback mechanism. This ensures that even under less-than-ideal conditions, our application remains functional.

To simplify the implementation of these technologies, we'll be relying on the robust socket.io library, a versatile tool that streamlines real-time communication complexities.

If you are not clear about WebSocket, then read my Primer on WebSockets

We will primarily equip WebSocket

Basic Skeleton: Keeping it Simple, Yet Functional

Before we dive into the intricacies, let's establish a basic skeleton for our application. We'll prioritize functionality over aesthetics at this stage, focusing on the core markup without delving into styling intricacies.

Server Side: Node.js

Now, let's shift our attention to the server side, where Node.js will play a pivotal role. Our Node.js server will wear multiple hats:

  • Frontend Serving: Ensuring users have a clean and intuitive interface to work with.

  • Event Dispatching: Facilitating the seamless exchange of information between clients, creating a harmonious collaborative environment.

With all these in mind, the server is written; you may see the full sample in the assets. But here are the crux of what is needed.

const io = socketIO(httpServer);

// ...snip for brevity...

io.on('connection', (socket) => {
  // Event listener for when the client sends a text message
  socket.on('textMessage', (message) => {
    // Broadcast the text message to all connected clients
    io.emit('textMessage', { message, userId: socket.id });
  });
});
  1. io.on('connection',... will fire the callback when a client requests a TCP connection.

  2. socket.on(<a-channel-name>,... will subscribe to the callback given to this client's connection when a message is sent on this particular channel.

  3. io.emit(<a-channel-name>, <payload-to-emit>) will send the given payload to other clients under this channel.

The above three steps are the minimal workflow with the socket.io library.

  • You connect to a server

  • Subscribe to message channels as needed

  • Emit messages to channels as needed

socket.io is implemented for many languages, visit their documentation for more.

Client Side: DOM API

Full code can be taken from the assets attached. But here are the focal pieces. First our stripped Text editor - semantic textarea - will fire a callback on input change.

 <textarea spellcheck="false" id="textInput" rows="25" cols="50" oninput="sendText()"></textarea>

This is the callback fired upon input...

function sendText() {
      const textInput = document.getElementById('textInput');
      const message = textInput.value;
      // then we emit the message
      socket.emit('textMessage', message);
}

...and update the universal state upon signal from the server...

function displayMessage(message, userId, color) {
      // ...snip...
      // sync the data
      textInput.value = message;
      // ...snip...
}

Believe it or not, this is all we need to have sync data between multiple clients. Note that socket.io hides many complexities. If you want to learn them too, please let me know in the comments and subscribe to my newsletter; so that you get notified immediately when I post.


EXTRAS: Enhancing Collaboration with Audit History

To know what's going on let's enhance our collaborative text area with an audit history. We want to capture every twist and turn in our users' collaborative journey. Leveraging the built-in userId from socket.io, we'll imprint each snapshot with a timestamp.

// we get the timestamp to string that is tailored Locale to user
new Date().toLocaleString()

This audit history will be neatly presented using the details HTML element, offering a collapsible view for a user-friendly experience.

<details>
  <summary>Audit</summary>
  <div>
    <!-- snapshots go here -->
  </div>
</details>

...and append new messages to the div using this DOM API.

This concise yet comprehensive structure showcases the progression of our collaborative efforts.


Conclusion

As we wrap up this coding adventure we've successfully created a real-time synced text area with an integrated audit history. WebSockets and the versatile socket.io library have played pivotal roles in this journey.

Before you embark on your coding endeavors, consider supporting my educational journey by subscribing to my newsletter (it's free btw) or following me on X.

Happy coding, and may your text areas sync flawlessly in the vast landscape of web development!