Create a Chat App With Node.js
Tags: node, javascript, developer, messaging
Published: 2024-04-30 13:34:18

[Node.js](https://nodejs.org/en) is an open-source [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript) runtime environment for building backend services and command line applications. This tutorial will guide you in creating an instant Node-based chat app that runs on a JavaScript [server](https://getstream.io/blog/set-up-coding-environment/ ) and outside a web browser.  

First, grab your favorite command-line tool, [Terminal](https://support.apple.com/guide/terminal/welcome/mac) or [Warp](https://www.warp.dev/), and a code editor, preferably [VS Code](https://code.visualstudio.com/) and let’s begin.


## Before You Start

Before proceeding with the tutorial, make sure you have the following. 

- Install [Node](https://nodejs.org/en/download): Download the latest prebuilt installer or use a package manager.  
- Install [Yarn](https://yarnpkg.com/) or [NPM](https://www.npmjs.com/) to add the required packages and modules.
- [Signup for a Stream account](https://getstream.io/try-for-free/): To get an API key and app secret. You will use these credentials later in this tutorial. 


## A Preview of What You Will Build


![A preview of the chat UI](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tgafju7wd94zf0oh13td.gif)

The image above illustrates the chat application you will build using Node and Stream’s chat API. Download the completed project from [GitHub](https://github.com/GetStream/stream-tutorial-projects/tree/main/JavaScript/NodeJSChatApp), explore the source code, and test it using VS Code. 
## Create a New Node Project

Launch your favorite command line tool and make a new directory with the command below.

`mkdir node-chat`

`cd` into the **node-chat** folder and initialize a new Node project with the following command. 

`yarn init -y`

The above command adds two files, **package.json** and **README.md**, to the folder **node-chat**.


## Install Server Dependencies

In this tutorial, we must build a Node server to run the chat app. Creating the server requires the following dependencies. 

- [Dotenv](https://www.npmjs.com/package/dotenv): A module for making environment variables from a **.env** file accessible to a **process.env** object.
- [Express](https://www.npmjs.com/package/express): A lightweight framework for building web applications. 
- [Stream Chat JS](https://github.com/GetStream/stream-chat-js): A real-time chat SDK for JavaScript.
- [Cors](https://www.npmjs.com/package/cors): A package for providing middleware for Node.js.
- [Body-parser](https://www.npmjs.com/package/body-parser): A body passing middleware for Node.js.

Enter the command below to install all the packages. If you prefer, you can use `yarn` instead of `npm` to add the packages. 

`npm add dotenv express stream-chat cors body-parser`


## Add Required Files in VS Code

Still in the project's root folder, **node-chat** in Terminal or using your chosen command line tool, enter `code .` to launch the project in VS Code. 

**Note**: Using `code .` to open VS Code only works if you have already enabled the feature in VS Code. 

Add a **.env** file to store the following Stream Dashboard credential placeholders in the project's root folder. You will replace the placeholder credentials shortly. 

```shell
PORT=5500
STREAM_API_KEY=<your api key>
STREAM_APP_SECRET=<your app secret>
```

Next, add a new file, **server.js** in the project's root folder and use the following sample code as its content. 

```javascript

// server.js
require('dotenv').config();

const express = require('express');
const cors = require('cors');
const bodyParser = require('body-parser');
const { StreamChat } = require('stream-chat');

const app = express();

app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

// initialize Stream Chat SDK
const serverSideClient = new StreamChat(
  process.env.STREAM_API_KEY,
  process.env.STREAM_APP_SECRET
);

app.post('/join', async (req, res) => {
  const { username } = req.body;
  const token = serverSideClient.createToken(username);
  try {
    await serverSideClient.updateUser(
      {
        id: username,
        name: username,
      },
      token
    );

    const admin = { id: 'admin' };
    const channel = serverSideClient.channel('team', 'general', {
      name: 'General',
      created_by: admin,
    });

    await channel.create();
    await channel.addMembers([username, 'admin']);

    res
      .status(200)
      .json({ user: { username }, token, api_key: process.env.STREAM_API_KEY });
  } catch (err) {
    console.error(err);
    res.status(500);
  }
});

const server = app.listen(process.env.PORT || 5500, () => {
  const { port } = server.address();
  console.log(`Server running on PORT ${port}`);
});
```

The **dotenv** package we added in the previous section will make the **.env** file available to the **process.env** object in the sample code above, **server.js**.


## Obtain App Credentials From Your Stream’s Dashboard

In this section, you will create a new app in your Stream’s dashboard and use your API key and the app's secret to populate the **.env** file you added earlier. If you do not have an account yet, [sign up](https://getstream.io/try-for-free/) for a free one. When you log in to your dashboard:

- Select the **Apps** category in the left sidebar and click the **Create App** button. 


![Stream dashboard](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/60fia0ay7owzkte81ivt.png)

- Fill in the required information to create the new in app in your Dashboard. 


![A new app screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uxrp17lukjjpaz3a0et0.png)

- When you finish the app creation, click the its name to access the **key** and secret. Copy them to fill their placeholders in the **.env** file.


![Credentials screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xpz1iwgbff01n7w5tcpm.png)

## Run the Server

At this point, we can run the server via the command line. Since we have already opened the app in VS Code, it is more convenient to use the built-in VS Code Terminal. 
1. Launch the Terminal in VS Code by navigating the toolbar and selecting **Terminal -> New Terminal**. 
2. Add the command `node server.js` to start the server. Run the command in the root folder, as illustrated in the image below. 


![Server running in VS Code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sy5irp1c1hlmq5qkja74.png)

You will now see the message **Server running on PORT 5500**. Congratulations 🎉 👏. You now have the server up and running. Let’s create a chat client in the next section. 


## Create a Chat Client

Our chat client will have the following dependencies: 

- [Axios](https://axios-http.com/docs/intro): An HTTP client for Node.js and the web. 
- [Prompt](https://www.npmjs.com/package/prompt): A command-line prompt for Node.js.
- [Ora](https://www.npmjs.com/package/ora): A dynamic loading spinner for Terminal.
- [Util](https://www.npmjs.com/package/util): A utility module for Node.js. 
- [Neo-blessed](https://www.npmjs.com/package/neo-blessed): A cursor-like library for Node applications.

Add the following command in the VS Code’s Terminal to install all the above dependencies. 

`npm add axios prompt ora util neo-blessed`

Next, add a new file, **app.js**, to the project’s root folder in VS Code. Use the sample code below to fill in the file’s content. 

```javascript

// app.js
const axios = require('axios');
const prompt = require('prompt');
const ora = require('ora');
const { StreamChat } = require('stream-chat');
const util = require('util');
const blessed = require('neo-blessed');

function fetchToken(username) {
  return axios.post('http://localhost:5500/join', {
    username,
  });
}

async function main() {
  const spinner = ora();
  prompt.start();
  prompt.message = '';

  const get = util.promisify(prompt.get);

  const usernameSchema = [
    {
      description: 'Enter your username',
      name: 'username',
      type: 'string',
      pattern: /^[a-zA-Z0-9\-]+$/,
      message: 'Username must be only letters, numbers, or dashes',
      required: true,
    },
  ];

  const { username } = await get(usernameSchema);
  try {
    spinner.start('Fetching authentication token...');
    const response = await fetchToken(username);
    spinner.succeed(`Token fetched successfully!`);

    const { token } = response.data;
    const apiKey = response.data.api_key;

    const chatClient = new StreamChat(apiKey);

    spinner.start('Authenticating user...');
    await chatClient.setUser(
      {
        id: username,
        name: username,
      },
      token
    );
    spinner.succeed(`Authenticated successfully as ${username}!`);

    spinner.start('Connecting to the General channel...');
    const channel = chatClient.channel('team', 'general');
    await channel.watch();
    spinner.succeed('Connection successful!');
  } catch (err) {
    spinner.fail();
    console.log(err);
    process.exit(1);
  }
}
main();
```

The above sample code uses the **Prompt** package we installed earlier to request the user's **username**. The **username** request is then sent to the server to retrieve a token for authentication. To verify that the user authentication works successfully, keep the server running with the command `node server.js`.


![A running server](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8lpb0060vutpm47vm7st.png)

While the server is still running, open another Terminal in VS Code and enter the command `node app.js` to receive a prompt to enter a user name. Press the return key after adding the user name. You will then get a similar result in the image below. 


![Connection sucessfull preview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s07l727kr1rd6uh0fwlh.png)

**Note**: If you get an **ora** package error after running `node app.js` in the Terminal, remove the package with `npm r ora`. Then, install an older version of [ora](https://www.npmjs.com/package/ora-classic/v/5.4.1) with the command, `npm i ora@5.4.1`.


## Create the Chat UI With Neo-blessed

Let's create a chat UI using the package called [Neo-blessed](https://github.com/mscdex/neo-blessed). Neo-blessed is a cursor-like library that helps developers to build Node-based Terminal interfaces. To create the chat UI, you should update the sample code in **app.js** with the following. 

```javascript

// app.js: updated
const axios = require('axios');
const prompt = require('prompt');
const ora = require('ora');
const { StreamChat } = require('stream-chat');
const util = require('util');
const blessed = require('neo-blessed');

function fetchToken(username) {
  return axios.post('http://localhost:5500/join', {
    username,
  });
}

async function main() {
  const spinner = ora();
  prompt.start();
  prompt.message = '';

  const get = util.promisify(prompt.get);

  const usernameSchema = [
    {
      description: 'Enter your username',
      name: 'username',
      type: 'string',
      pattern: /^[a-zA-Z0-9\-]+$/,
      message: 'Username must be only letters, numbers, or dashes',
      required: true,
    },
  ];

  const { username } = await get(usernameSchema);
  try {
    spinner.start('Fetching authentication token...');
    const response = await fetchToken(username);
    spinner.succeed(`Token fetched successfully!`);

    const { token } = response.data;
    const apiKey = response.data.api_key;

    const chatClient = new StreamChat(apiKey);

    spinner.start('Authenticating user...');
    await chatClient.setUser(
      {
        id: username,
        name: username,
      },
      token
    );
    spinner.succeed(`Authenticated successfully as ${username}!`);

    spinner.start('Connecting to the General channel...');
    const channel = chatClient.channel('team', 'general');
    await channel.watch();
    spinner.succeed('Connection successful!');
    process.stdin.removeAllListeners('data');

    const screen = blessed.screen({
      smartCSR: true,
      title: 'Stream Chat Demo',
    });

    var messageList = blessed.list({
      align: 'left',
      mouse: true,
      keys: true,
      width: '100%',
      height: '90%',
      top: 0,
      left: 0,
      scrollbar: {
        ch: ' ',
        inverse: true,
      },
      items: [],
    });

    // Append our box to the screen.
    var input = blessed.textarea({
      bottom: 0,
      height: '10%',
      inputOnFocus: true,
      padding: {
        top: 1,
        left: 2,
      },
      style: {
        fg: '#787878',
        bg: '#454545',

        focus: {
          fg: '#f6f6f6',
          bg: '#353535',
        },
      },
    });

    input.key('enter', async function() {
      var message = this.getValue();
      try {
        await channel.sendMessage({
          text: message,
        });
      } catch (err) {
        // error handling
      } finally {
        this.clearValue();
        screen.render();
      }
    });

    // Append our box to the screen.
    screen.key(['escape', 'q', 'C-c'], function() {
      return process.exit(0);
    });

    screen.append(messageList);
    screen.append(input);
    input.focus();

    screen.render();

    channel.on('message.new', async event => {
      messageList.addItem(`${event.user.id}: ${event.message.text}`);
      messageList.scrollTo(100);
      screen.render();
    });
  } catch (err) {
    spinner.fail();
    console.log(err);
    process.exit(1);
  }
}
main();
```

The previous content of **app.js** authenticated the user and printed a message about the authentication on the screen. Let’s take a look at the following changes.

```javascript
const screen = blessed.screen({
      smartCSR: true,
      title: 'Stream Chat Demo',
    });

    var messageList = blessed.list({
      align: 'left',
      mouse: true,
      keys: true,
      width: '100%',
      height: '90%',
      top: 0,
      left: 0,
      scrollbar: {
        ch: ' ',
        inverse: true,
      },
      items: [],
    });

    // Append our box to the screen.
    var input = blessed.textarea({
      bottom: 0,
      height: '10%',
      inputOnFocus: true,
      padding: {
        top: 1,
        left: 2,
      },
      style: {
        fg: '#787878',
        bg: '#454545',

        focus: {
          fg: '#005fff',
          bg: '#353535',
        },
      },
    });
```

In the updated version of **app.js**, we used the Neo-blessed package to create a simple chat interface consisting of a text area and a container for holding a list of messages as shown in the code snippet above. 


**Retrieve and Send a Message**

```javascript
    input.key('enter', async function() {
      var message = this.getValue();
      try {
        await channel.sendMessage({
          text: message,
        });
      } catch (err) {
        // error handling
      } finally {
        this.clearValue();
        screen.render();
      }
    });
```

In this code snippet, when a user types a new message and presses the return key, the message is retrieved and sent to the message channel using the Stream Chat's `sendMessage` method. Head to our documentation to learn more about [sending simple messages](https://getstream.io/chat/docs/react/send_message/?language=js) using Stream. 


## Run the Node Server and the Chat App

Open a new Terminal window in VS Code and run the Node server with `node server.js`. Next, launch another Terminal window and run the updated chat app with `node app.js`. 

Bravo 👏. You will now see a chat interface similar to the one in the image below, allowing you to enter and send instant text messages. 


![A preview of the chat UI](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aldmbif3pv185x489j5a.gif)

## Chat Between Multiple Terminal Instances

When the app is running after performing the previous steps, you can open an additional terminal instance and chat between them. Here, you should authenticate yourself with a different user name and tile the Terminal windows vertically or horizontally in VS Code. You can now send and receive messages between the two authenticated users, similar to the demonstration in the video below. 


![A preview of two users chatting](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/if8b8d77ugs1foqg4xda.gif)


## Wrap Up

In this tutorial, you learned how to create a simple chat application using Node.js. You made a backend server that authenticates chat users with Stream's Dashboard credentials. Eventually, you discovered how to send and receive messages among multiple users in a channel. The Node.js chat application you built in this tutorial demonstrates a basic use case of what you can build with Node and Stream's JavaScript SDK for chat messaging. Check out the related links to learn more and discover where to go from here.