DEV Community

loading...

Chat inside of Terminal with NodeJS

Arsenii Gorushkin
Backend developer who wants to be Fullstack and DevOps Engineer
・6 min read

Hello Everyone!

Today I will show you how to make a small chat in terminal using Web Sockets with Nodejs. If you think you can do it better, then feel free to leave a suggestion, but otherwise let's get to the tutorial :D. After every step there will be a description explaining what the code shown above does.

This is what chat would look like by the end of the project

Chat in working condition

P.S. (If you are stuck, feel free to go to the Github repo and copy code from there)

Prep Phase

Let's first do few things -

  • Create package.json, and write {"type":"module"} for ES6 imporsts
  • Install ws using npm i ws

Step 1

Now, let's create server.js, for our websocket server and paste this code

import WebSocket from "ws";

const serverSocket = new WebSocket.Server({ port: 8080 });

export const clientSockets = [];

serverSocket.on("connection", (clientSocket) => {
  clientSockets.push(clientSocket);
});

serverSocket.on("listening", () => console.log("Server is online!"));
Enter fullscreen mode Exit fullscreen mode

Description: This enables our server to be hosted on port 8080, as well as stores a client socket in an array when it is connected

Step 2

Let's now go and write some code for our client inside client.js

import WebSocket from "ws";

const clientSocket = new WebSocket("ws://localhost:8080");

clientSocket.on("open", () => {
  console.log("Connected to the server!");
});
Enter fullscreen mode Exit fullscreen mode

Description: This is some basic code for our client which connects to localhost on port 8080. We will add more interactivity in coming steps.

Step 3

Part 1 of Step 3

We need to somehow identify out client, don't we? So let's allow them to pick their own name. Before that make a folder path like that shown ./src/client/, inside there we will be putting all the modules for our client in future. For now make a file called readline.js inside ./src/client/ and put following code inside.

import Readline from "readline";

export const readline = Readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});
Enter fullscreen mode Exit fullscreen mode

Description: This export is to make our life a little bit easier by being able to import readline with just one line of code in many different files. Readline will be used to fetch user's messages and user's responses

Part 2 of Step 3

Now let's make a script that will ask user for their name, so we can later store it. Make a getName.js file inside our client scripts folder, and paste following code into it

import { readline } from "./readline.js";

export const getName = () => {
  return new Promise((resolve, reject) => {
    readline.question("What would you like to be called?\n> ", (answer) => {
      console.log("You can now start messaging!");
      resolve(answer);
    });
  });
};
Enter fullscreen mode Exit fullscreen mode

Description: At first this code returns a promise, this is made because the client doesn't respond straight away and takes some time before writing out their name. This will allow us to async await client's name

Part 3 of Step 3

Now, let's think of a scenario where to different users have the same name, thought about that? In that case we would need a unique identifier for each user. That means we will write a small function that will generate a token for a user. Create a createToken.js in client scripts, and paste in this code

export const createToken = () =>
  "x"
    .repeat(20)
    .replace(/x/g, () => Math.floor(Math.random() * 16).toString(16));
Enter fullscreen mode Exit fullscreen mode

Description: This code generates a 20 letter long token consisting of hexadecimals. There is a slight possible chance that 2 users will have same token, but they are so small that I don't even know what this number 1.2089258e+24 (Other then it having e in it), so we can consider this a fool proof plan.

Part 4 of Step 3

Let's think of how will we send messages. It seems like getting user input and sending it to the server will be the best idea, so we'll do it that way. Make a script called getMessage.js in client scripts (Who would've thought lol), and paste following code in there.

import { readline } from "./readline.js";

export const getMessage = (clientSocket, username, token) => {
  readline.question("> ", (answer) => {
    clientSocket.send(
      JSON.stringify({
        username: username,
        token: token,
        content: answer,
      })
    );
    getMessage(clientSocket, username, token);
  });
};
Enter fullscreen mode Exit fullscreen mode

Description: What this code does, is it takes a client socket, name, and token as parameters of the function and calls a readline question, which waits for user input and then puts all the data inside a stringified object, and then sending it to the server for being parsed there. This function calls upon itself, so that we can get infinite message input

After we've done that, let's import and paste the code inside our client.js file like shown below

import WebSocket from "ws";

import { getName } from "./src/client/getName.js";
import { getMessage } from "./src/client/getMessage.js";
import { createToken } from "./src/client/createToken.js";

const clientSocket = new WebSocket("ws://localhost:8080");

clientSocket.on("open", async () => {
  console.log("Connected to the server!");
  const token = createToken();
  const username = await getName();
  getMessage(clientSocket, username, token);
});

Enter fullscreen mode Exit fullscreen mode

Step 4

Part 1 of Step 4

Now that we have done message sending, we need to make a script that will parse in on server, and send it to all the clients. This is where clientSockets array comes in handy. Let's first make a function that sends messages to all clients, but before that let's make a folder for our server inside ./src/server/ and put a file named sendAll.js in there. From now on this will be our server scripts folder. Inside sendAll.js we'll put code below in

import { clientSockets } from "../../server.js";

export const sendAll = (message) => {
  clientSockets.forEach((clientSocket) => {
    clientSocket.send(message);
  });
};
Enter fullscreen mode Exit fullscreen mode

Description: This function send message passed as a parameter to all clients by cycling through the array that we made earlier

Part 2 of Step 4

Let's now put this function inside the code of our server.js file. Whilst we add the function, we also need to add an event handler for message, so we can pass the message that we receive from the client into the function.

import WebSocket from "ws";

import { sendAll } from "./src/server/sendAll.js";

const serverSocket = new WebSocket.Server({ port: 8080 });

export const clientSockets = [];

serverSocket.on("connection", (clientSocket) => {
  clientSockets.push(clientSocket);
  clientSocket.on("message", (message) => sendAll);
});

serverSocket.on("listening", () => console.log("Server is online!"));
Enter fullscreen mode Exit fullscreen mode

Description: Now that we added this function, it will run whenever server receives a message from one of it's clients

Step 5

Part 1 of Step 5

Now that we have finally finished our code for the server (which we did), it's time to add a message fetcher for our client, so it can display the message in the console. Make a fetchMessage.js file inside our client scripts and paste next code inside.

export const fetchMessage = (message, token) => {
  const messageObject = JSON.parse(message);
  if (messageObject.token === token) return;

  const stdout = process.stdout;

  stdout.clearLine();
  stdout.write(`${messageObject.username}: ${messageObject.content}`);
  stdout.write("\n> ");
};
Enter fullscreen mode Exit fullscreen mode

Description: That function will do the displaying part of the chat, it will clear our current output line, put message in, and then add > to the next line, so that we continue typing there.

Part 2 of Step 5

Now all is left to add, is import of the fetchMessage function to the client.js, which will result into the code below.

import WebSocket from "ws";

import { getName } from "./src/client/getName.js";
import { getMessage } from "./src/client/getMessage.js";
import { createToken } from "./src/client/createToken.js";

import { fetchMessage } from "./src/client/fetchMessage.js";

const clientSocket = new WebSocket("ws://localhost:8080");

clientSocket.on("open", async () => {
  console.log("Connected to the server!");
  const token = createToken();
  const username = await getName();
  getMessage(clientSocket, username, token);

  clientSocket.on("message", (message) => fetchMessage(message, token));
});
Enter fullscreen mode Exit fullscreen mode

Step 6 - Enjoy

Run node server.js in one terminal, open two others and run node client.js on both of them and enjoy your own small chat inside a terminal! Feel free to modify it if you want, and tell me the things I could have done better.

If you liked this, then please leave a reaction and or a comment with the feedback on whether you like this post or not, it would mean a lot to me. I hope you have a great day, and cya later!

Discussion (3)

Collapse
elielsonaa profile image
Elielson de Andrade

Wow so good, fantastic post. thanks.

Collapse
agorushkin profile image
Arsenii Gorushkin Author

Thnak you for feedback!

Collapse
mark_alot profile image
Mark_ALot

Registered just to say
Awesome!
i cant wait to test this out!!!