DEV Community

Cover image for Create a Chat App With Node.js
Amos Gyamfi
Amos Gyamfi

Posted on • Originally published at getstream.io

Create a Chat App With Node.js

Node.js is an open-source 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 and outside a web browser.

First, grab your favorite command-line tool, Terminal or Warp, and a code editor, preferably VS Code and let’s begin.

Before You Start

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

  • Install Node: Download the latest prebuilt installer or use a package manager.
  • Install Yarn or NPM to add the required packages and modules.
  • Signup for a Stream account: 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

The image above illustrates the chat application you will build using Node and Stream’s chat API. Download the completed project from GitHub, 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: A module for making environment variables from a .env file accessible to a process.env object.
  • Express: A lightweight framework for building web applications.
  • Stream Chat JS: A real-time chat SDK for JavaScript.
  • Cors: A package for providing middleware for Node.js.
  • 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.

PORT=5500
STREAM_API_KEY=<your api key>
STREAM_APP_SECRET=<your app secret>
Enter fullscreen mode Exit fullscreen mode

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


// 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}`);
});
Enter fullscreen mode Exit fullscreen mode

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 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

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

A new app screen

  • 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

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

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: An HTTP client for Node.js and the web.
  • Prompt: A command-line prompt for Node.js.
  • Ora: A dynamic loading spinner for Terminal.
  • Util: A utility module for Node.js.
  • 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.


// 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();
Enter fullscreen mode Exit fullscreen mode

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

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

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 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. 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.


// 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();
Enter fullscreen mode Exit fullscreen mode

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.

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',
        },
      },
    });
Enter fullscreen mode Exit fullscreen mode

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

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

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 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

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

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.

Top comments (0)