Building a Contextual Chat Feature Using a Front-End Only Architecture

Ankit Karnany
The Observables
Published in
6 min readJun 6, 2024
Photo by Volodymyr Hryshchenko on Unsplash

As a front-end developer, I always wondered how chats are built. I used to think it required extensive backend and infrastructure work to implement a chat feature in any web application. However, after weeks of research and long hours of development, I was thrilled to discover that a fully functional chat feature can be built using just the front end. Additionally, self-hosting an open-source chat is easy due to the extensive documentation available.

I’d like to share my experience of building a chat feature for our product. In this article, I’ll address several key questions that one might have:

  1. What do I mean by context?
  2. How can users share information within a closed context?
  3. How can we build such a feature in a web application?
  4. What kind of architectural decisions do we need to make?
  5. Can we approach this with domain-driven design?

By the end of this article, you’ll have a clear understanding of how to create a contextual chat feature using only front-end technologies.

Problem Statement

In a certain part of the web application, users should be able to start a conversation and exchange information within that context. The information can be in any form — message, attachment, comment, etc. Only the users involved in the conversation should be able to see the messages and participate. For other users, these conversations might be confusing or unnecessary.

What is a context?

Imagine you are a veterinarian who received a dog’s x-ray report on a teleradiology platform. You want to ask the veterinarian something specific about the report he wrote. You should be able to start a chat within the context of that dog’s report, allowing only relevant people to exchange information within that report’s context.

In this scenario, the context is the dog’s x-ray report. The chat should be tied to this specific context, ensuring that only those involved in the case can access and contribute to the conversation. This approach helps in maintaining the relevance and clarity of the information being shared, preventing unrelated or unnecessary messages from cluttering the communication space.

Domain Definition

We follow a domain-driven design (DDD) pattern in our product. Thus, I’d like to explain the domain entities we created for the chat feature. From the example above, in the most basic form, there are three key entities:

  1. Users: Individuals who use the chat feature.
  2. Context: The specific scenario or subject around which the chat is centered.
  3. Messages: The information exchanged within the context.
Domain Definition

To summarize, we define a user who can authenticate into a self-hosted chat service and receive an API key. With this API key, users can make subsequent calls to the chat service. These calls might include:

  1. Create a context: Establishing a new conversation thread specific to a scenario.
  2. Join or add users to a context: Including other relevant users into the conversation.
  3. Send messages in a context: Exchanging information within the established context.

These basic domain entities and their interactions form the foundation of our chat feature, enabling a structured and focused communication platform within the application.

Self-hosted Chat

There are many open-source chat frameworks that you can host on any server provider, such as AWS, Azure, Google Cloud, and others. Some popular chat services include Rocket.Chat, Zulip, Mattermost, and Matrix. Each of these platforms provides extensive documentation with step-by-step guides for setting up and hosting their services on a server. With basic knowledge of server hosting, you can follow these steps and have a chat service up and running in no time.

Most of these services come with a client-side application once you host them on your server. This means you’ll be able to manage chats directly from those client interfaces, making it easier to integrate and maintain the chat feature within your application.

By leveraging these open-source frameworks, you can avoid the complexities of building a chat service from scratch while still maintaining control over your data and infrastructure. This approach allows you to focus on tailoring the chat functionality to fit your specific needs and ensures a robust, scalable solution.

Architecture

In our architecture, the app and the chat are hosted separately. The Backend for Frontend (BFF) pattern is used to manage the communication with the chat API. The reason for using BFF is to keep the app independent of the open-source chat server. This allows us to replace the open-source chat service with another one at any time without changing the app itself.

Architecture Overview

Single Page Application

Having defined the domain for our chat feature, we now outline the architecture surrounding this domain for our SPA.

SPA Architecture

For the app, the architecture is quite straightforward. At the core, we have the business logic encapsulated in the domain. Surrounding that, we have created the infrastructure, which includes the API and store implementation. Finally, we expose the UI with several action points derived from the use cases.

BFF Interface

For simplicity, we have defined the same domain for the BFF as we did for the SPA. The most critical aspect of the BFF is its interface, where we built APIs that communicate with the Zulip chat server and exposed those APIs to the app.

BFF Architecture

Chat Server

We chose Zulip as the chat server for our feature. Here are the key endpoints that we needed to call from our BFF interface to achieve the desired behavior.

Chat Server — Zulip

In our BFF, authentication involves creating a user if it doesn’t exist and fetching the API key to allow future communication with the Zulip endpoints. After that, we enable users to create a stream(context) and add users to that stream(context), thereby enclosing the user group within the context where messages can be exchanged.

Directory Structure

SPA Directory Structure

  • Core domain logic
  • Infrastructure (API, Store implementation and UI components)
  • Use cases (action points)

BFF Directory Structure

  • Domain definitions
  • API interfaces for communication with Zulip
Basic directory Structure For SPA and BFF

This architecture ensures a modular and flexible approach, allowing us to maintain, update, or replace components without disrupting the overall functionality of the application.

Conclusion

Building a contextual chat feature using only front-end technologies is not only feasible but also efficient, thanks to the extensive documentation and flexibility provided by open-source chat frameworks. By leveraging a domain-driven design approach, we were able to create a robust architecture that integrates seamlessly with our single-page application. The use of a Backend for Frontend (BFF) pattern ensures that our application remains decoupled from the chat service, offering the flexibility to switch out chat servers without significant changes to the app.

Through this process, we highlighted the importance of defining clear domain entities and maintaining a modular architecture. By focusing on the core principles of context and user-centric design, we developed a chat feature that enhances user experience by providing relevant, context-specific communication.

I hope this article provides you with the insights and guidance needed to implement your own chat feature using front-end technologies. With the right tools and architectural approach, you can build a scalable and maintainable chat system that meets your application’s unique needs.

Thanks for reading! Hope you learned something.

--

--