Implement Stream Chat with Vanilla JS

11 min read
Eze S.
Eze S.
Published February 12, 2020

In this post, we are going to implement a simple Group Chat application with Vanilla Javascript and Stream Chat!

Stream Chat allows you to rapidly ship real-time messaging systems that are reliable and robust, without the overhead cost and time of managing the infrastructure by yourself. And it’s pretty easy to use...

You’ll find out how easy it is to plug in a messaging module (with all the cool features) to your existing application or to build something new and exciting with the Stream SDK/API from a simple chat application to something even more complex, and you don’t have to worry about building all those functionalities yourself!

Let’s get started!

Create an Account on Stream

Creating an account on Stream is pretty straight forward.

Visit the Stream website and Sign up:

Once your account is created successfully, you can quickly log in to your dashboard to get your app secret and key as shown below:

You'll need the API key and secret to authenticate with the Stream API. Keep it safe 🙂

Set Up

Let’s set up the application structure by creating some directories and installing Stream...

To create your working directory and move into that directory, run:

sh
mkdir chat-application && cd chat-application

Then make your directory to look like what we have below:

    ├── dashboard.bundle.js
    ├── public
    │   ├── index.html
    │   └── static
    │       ├── login.css
    │       └── style.css
    └── src
        ├── Dashboard
        │   └── index.js
        └── Utils
            └── config.js

Let’s start with the UI!

We are going to use the following html template for our chat application; add this template to your public/index.html file:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link src='https://use.fontawesome.com/releases/v5.0.13/js/all.js'></link>
    <link rel="stylesheet" href="./static/style.css">
    <title>Stream Chat App </title>
</head>
<body>
    <section class="msger">
        <header class="msger-header">
          <div class="msger-header-title">
            <i class="fas fa-comment-alt"></i> Stream Chat App

Then, download this CSS file and add it to the /public/static/index.css file.

Now, you should have the UI up and running, but it doesn’t do anything. It’s just a dumb HTML and CSS web page:

So, let’s get Stream to add some functionality to the html page!

Did you notice that we are loading a bundled version of our javascript file to the HTML page?

html
<script src="./../dashboard.bundle.js" type="module"></script>

That’s because we're using the Stream Chat NPM module! So, we are going to install Stream Chat with NPM or yarn and then use browserify to bundle everything into dashboard.bundle.js.

You can install browserify globally; it’s not required for your application to work, it’s a tool to help us bundle npm modules and use them in the browser:

sh
npm install -g browserify

So, in your root directory (chat-application), run:

sh
npm i stream-chat

Now, we are all set up! Let’s get coding 🙂

To prepare, open the dashboard index file src/Dashboard/index.js; this is where we’ll write our chat logic.

└── src
    ├── Dashboard
    │   └── index.js

Group Chat Code

Add the code below to the src/Dashboard/index.js file:

js
const Stream = require("../../node_modules/stream-chat/dist/index.js");
const config = require("../Utils/config");
const { StreamChat } = Stream;
const { BubbleTemplate } = config;
const messsageText = document.getElementById("message-input");
const info = document.getElementById("info");
const token = "fdjfknfdfmdbfknmbhvcwewf";
const apiKey = "bfkjbeifjkbjwe";

const client = new StreamChat(apiKey);
//initialize user
client.setUser(
  {
    id: "userid",
    name: `First Name Last Name`,

And this to the src/Utils/configuration.js file:

js
const TimeAgo = require("../../node_modules/javascript-time-ago");

// Load locale-specific relative date/time formatting rules.
const en = require("javascript-time-ago/locale/en");
TimeAgo.addLocale(en);

const timeAgo = new TimeAgo("en-US");

const config = {
  BubbleTemplate: (name, id, text, date, image) => {
    return `
      <div id="userImage" style="background-image: url(${image})"
      class="msg-img">
          </div>
              <div class="msg-bubble">

Now, let’s dig into the code and see how it all comes together...

Right here, we are importing Stream and importing the chatbubble template, which we defined in the config file:

js
const Stream = require("../../node_modules/stream-chat/dist/index.js");
const config = require("../Utils/config");

const { StreamChat } = Stream;
const { BubbleTemplate } = config;

const messsageText = document.getElementById("message-input");
const info = document.getElementById("info");

const token = "fdjfknfdfmdbfknmbhvcwewf";
const apiKey = "bfkjbeifjkbjwe";
Chat bubble

The BubbleTemplate is a function that returns HTML when we pass the name, id, text, date, and the image url of the bubble. We need this bubble template because each time we receive a message from the group, we want to push it to the UI as a bubble containing a message in a group chat. We can easily pass the message object info to this function and re-use it as many times as we want to:

BubbleTemplate(name, id, text, date, image)

Next, let’s select the message input field and define some important information:

js
const messsageText = document.getElementById("message-input");
const info = document.getElementById("info");
const token = "<USER_TOKEN>";
const apiKey = "<YOUR_API_KEY>";

In reality, the token should be generated after you log in/create a user for your Stream application using the Stream API. You can start by using this open-source Stream API to log users in.

Run this command in a different directory to clone the open-source API and install the dependencies:

sh
git clone https://github.com/astrotars/stream-chat-api && cd stream-chat-api && npm i

The API uses MongoDB to store user data; create a MongoDB database with Mongo Atlas and get your MongoDB URI. Then, get your API Secret and Key from your Stream Dashboard, as explained earlier.

Building your own app? Get early access to our Livestream or Video Calling API and launch in days!

Create a .env file in the root directory of the API and add the following configuration to it:

sh
NODE_ENV=development
PORT=8080
STREAM_API_KEY=YOUR_STREAM_API_KEY
STREAM_API_SECRET=YOUR_STREAM_API_SECRET
MONGODB_URI=YOUR_MONGODB_URI

Then, run

sh
npm start

to start up the API; it should be running on the PORT=8080, as defined in the .env file.

You can now create a user using Postman by sending a POST request to this url http://localhost:8080/v1/auth/init, with this data:

{
  "name": {
          "first": "First Name",
          "last": "Last Name"
  },
  "email": "foo@bar.baz",
  "password": "qux"
}

Copy the token and apiKey, and user id from the response. Switch back to your chat application src/Dashboard/index.js file we’ve been looking at and update the token and apiKey with the token and key from the response:

js
const token = "<USER_TOKEN>";
const apiKey = "<YOUR_API_KEY>";

It would be fun if you can create a registration interface to make an api call to this api and store the data in local storage after registration...

You can start with something like this:

js
const axios = require("../../node_modules/axios/index.js");

const email = document.getElementById("email");
const password = document.getElementById("password");
const lastname = document.getElementById("lastname");
const firstname = document.getElementById("firstname");
const submit = document.getElementById("submit");

submit.addEventListener("submit", (e) => {
  e.preventDefault();
  initStream(e);
});

const initStream = async (e) => {
  e.preventDefault();

and an html sign up page like this:

html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <!-- <script src="https://cdn.jsdelivr.net/npm/stream-chat"></script> -->
        <link src='https://use.fontawesome.com/releases/v5.0.13/js/all.js'></link>
        <link rel="stylesheet" href="./static/login.css">
        <title>Stream Chat App </title>
    </head>
    <body>
        <div class='container'>
            <form method='post' id="submit">
              <h1>

That’s like a pretty good starting point...

So, now that we understand what is going on here:

js
const Stream = require("../../node_modules/stream-chat/dist/index.js");
const config = require("../Utils/config");

const { StreamChat } = Stream;
const { BubbleTemplate } = config;

const messsageText = document.getElementById("message-input");
const info = document.getElementById("info");
const token = "fdjfknfdfmdbfknmbhvcwewf";
const apiKey = "bfkjbeifjkbjwe";

Let’s move on to the next bit of code!

Setting A User

Setting a user in Stream Chat is pretty simple. Create a new instance of StreamChat called client by passing in your API key to the constructor. This will serve as an entry point to access the StreamChat functionality.

Next, we initialize a new user. You can get all the information you need to set a user in the response you receive from the API call you made initially:

js
const client = new StreamChat(apiKey);

//initialize user
client.setUser(
  {
    id: "userid",
    name: `First Name Last Name`,
    image: "https://image.flaticon.com/icons/svg/145/145867.svg",
  },
  token,
);

Set The Channel Details And Subscribe to Further Updates

So, what channel do you want your users to be in? You can specify the channel information in the channel.client function. The channel receives three parameters. The first is the channel type (we'll use 'messaging'), and the other are the channel ID and details you can specify as many details as you want):

js
// Set Channel details
const channel = client.channel("messaging", "General", {
  name: "Awesome channel about traveling",
});

// fetch the channel state, subscribe to future updates
const state = channel.watch();

Send a Message to The Group

Next, we'll plug in an event listener to the messageInput (the input box you type your messages into) that we defined initially.

We'll listen to your key presses with the checkTyping function and let everyone know that you are typing:

js
const checkTyping = async (e) => {
  if (e.type === "keypress") {
    // sends an event typing.start to all channel participants
    await channel.keystroke();
  }
};

and when you finish and hit enter, we'll catch that using the keyCode 13, and then send the message with channel.sendMessage:

js
// Send message to the channel
messsageInput.addEventListener("keypress", (e) => {
  checkTyping(e);
  if (e.keyCode == 13) {
    // clean message input a bit
    const text = e.target.value.replace(/</g, "<").replace(/>/g, ">").trim();

    if (text === "") {
      return -1; //empty messages cannot be sent
    } else {
      channel.sendMessage({
        text: e.target.value,
      });
      e.target.value = "";
    }
  }
});

Listening For New Messages

If any member of the channel sends a message, we get notified using the message.new event. We look at the messages in that channel state and pick the latest message and push it to the UI.

js
// Listen for new messages
channel.on("message.new", (event) => {
  const message = channel.state.messages[channel.state.messages.length - 1];
  //push the new message to the UI
  singleMessageDisplay(message);
});

Loading Historical Messages

The first time we open our app, the first thing we want is to load our previous conversations so we can start up from there...

That is what this part of the code does. It gets the state data, and pushes it to the UI:

js
// What is our current channel state? We get to know that from here
async function getState() {
  return await state;
}

// Get historical messages
getState().then((data) => {
  document.getElementById("loading").textContent = "";
  data.messages.map((message) => {
    singleMessageDisplay(message);
  });
});

Last, but not least, let’s talk about the singleMessageDisplay function; this function just stacks the latest messages (using the bubble template) to the UI. If the message is from you, it stacks it to the right, and if it’s from anyone else in the group, it stacks it to the left:

js
// Push single message to display. This function pushes a chat bubble to the UI when you hit enter
const singleMessageDisplay = (message) => {
  if (message.user.id === client.user.id) {
    const div = document.createElement("div");
    div.className = "msg right-msg";
    div.innerHTML = BubbleTemplate(
      message.user.name,
      message.user.id,
      message.text,
      message.created_at,
      client.user.image,
    );
    document.getElementById("right-msg").appendChild(div);
  }

So, far here is what we have:

Our application is ready! Here is how the final chat application works:

You can find the completed project on Github here.

A Few Notes

I'd advise using browserify to bundle your javascript code like so:

sh
browserify src/Dashboard/index.js -o dashboard.bundle.js

Wrapping Up

Creating a chat application with Vanilla Javascript and Stream is pretty simple, and there is so much more you can do with Stream, this is just scratching the surface...

Check out the docs to learn more! I challenge you to add even more functionalities to the chat application after reading the docs...

I can’t wait to see the awesome stuff you’ll build with Stream and Javascript!

decorative lines
Integrating Video With Your App?
We've built an audio and video solution just for you. Launch in days with our new APIs & SDKs!
Check out the BETA!