Building a Chat Application: Step-by-Step Guide —Bootstrapping an Application— Part2
Learn how to build a real-time chat application from scratch. This step-by-step guide covers all the key features of a chat app with working code

As a software engineer, I have a strong passion for creating innovative and efficient solutions through code. I am a quick learner and always eager to expand my skills and knowledge. I enjoy working in a team environment. I am able to explain technical concepts to non-technical team members in a clear and understandable manner.
This is a Part 2 of my Building a Chat Application Series. I’ve added tags in my GitHub Repository in case you want to follow along with this article or any upcoming articles of this Series. For this article, you can check all the source code in my GitHub Repository here.
Installing Dependencies
I am using TypeScript, Express, and Socket.io as the main tools to build a chat application. Although there will be other libraries involved, these three are the core components that make up the whole application.
Lets install the required dependencies
npm i express socket.io
npm i -D @types/express @types/node @types/socket.io nodemon typescript
I plan to use dependency injection to make writing tests easier. Currently, I have not set up a dependency injection library, but I will incorporate one in future articles.
TypeScript Support
Let’s create a .tsconfig.json file in the root to enable TypeScript. I am using basic settings that you’ll typically use in most projects. There are additional settings we’ll need to add when we implement the dependency injection library, but for now, let’s keep it simple.
{
"compilerOptions": {
"experimentalDecorators": true,
"module": "commonjs",
"esModuleInterop": true,
"target": "es6",
"noImplicitAny": true,
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist",
"baseUrl": ".",
"paths": {
"*": ["node_modules/*"]
}
},
"include": ["src/**/*"]
}
Docker
I am using MongoDB as the database, so let’s create a Dockerfile for MongoDB to easily spin up an instance when needed, instead of installing it locally on our system. Installing MongoDB manually can be a hassle, especially if you need to upgrade to a new version, as you may need to first uninstall the previous version, and this process can be tricky if some of your projects depend on the older version. Using Docker eliminates these issues and provides several other benefits.
Instead of creating a Dockerfile, let’s create a docker-compose.yml file. The advantage of using docker-compose over a Dockerfile is that when we run the container, we can specify settings such as volumes for data persistence, environment variables, and others in a centralized location. By creating a docker-compose.yml file, we can streamline the process of building a MongoDB image.
version: "3.3"
services:
mongodb:
container_name: "mongodb"
image: "mongo"
volumes:
- chat_app:/data/db
ports:
- "27017:27017"
volumes:
chat_app:
As you can see, I am using basic YAML configurations. I am not including password or environment variables, as these are not the focus of this series. The main objective is to build a practical chat application with essential features for daily use. If desired, you can add more configurations to the docker-compose.yml file.
Note: Using Docker is not a requirement for this series. If you prefer to manually install a MongoDB instance, you can simply skip the Docker portion.
Folder Structure

As you can see from the image, the folder structure is straightforward. All code related to the REST API will be stored in the “rest” directory, and all code related to Socket.IO will be stored in the “ws” directory. Any code that is shared between the REST API and Socket.IO will be placed in the “commons” directory, such as database models, services, repositories etc...
REST API Setup
I am using Express to build a REST API, so let’s create a server for the REST API. This is just for initial setup. In the coming articles, I will create all the routes, implement database logic, and complete the application. This is just the starting point.
import { Server } from "http";
import express from "express";
import allRoutes from "./routes";
const app = express();
// setup middlewares
app.use(express.json());
app.use(express.static("public"));
app.use("/", allRoutes);
function listen(port: number): Server {
return app.listen(port, () => {
console.log(`App is listening on the PORT ${port} ...`);
});
}
export { listen };
The listen() method returns the HTTP server, which can then be passed to Socket.io. The reason for this is to have the Socket.io server listen on the same HTTP server and port. You are also free to use a different port if desired.
The routes file is simple at the moment, with only one route, called a health check route. When the application is deployed, it is important to check if the server is up and running. This route can be used to ping the server and determine if it is up or down.
import { Router } from "express";
const router = Router();
router.get("/", (_, res) => {
res.json({ message: "Chat Application!" });
});
export default router;
Socket.io Server Setup
Socket.io is a JavaScript library that enables real-time, bidirectional communication between a client and a server. It abstracts the complexities of WebSockets and provides a simple, event-driven API for web applications. Socket.io supports auto-reconnection, room management, and broadcasting of messages to multiple clients. Socket.io uses WebSockets as the underlying transport mechanism but also falls back to other technologies (e.g. long polling) when WebSockets are not available.
Now let's create a socketio server:
import { Server } from "socket.io";
import { Server as HttpServer } from "http";
import chatNamespace from "./namespaces/chat";
import { IO, Namespace, WSType } from "./utils/ws.type";
let wsServer: IO;
let chatNS: Namespace;
function listen(httpServer: HttpServer, options?: WSType): IO {
wsServer = new Server(httpServer, {
...options,
cors: { origin: "*" },
pingInterval: 2000,
pingTimeout: 1000,
});
chatNS = wsServer.of("/chat");
chatNS.on("connection", chatNamespace(wsServer, chatNS));
return wsServer;
}
export { listen };
As you can see, I am not listening on a different port. If you prefer to have the Socket.io server listens on a different port, such as 5000, you can do this by specifying the port as follows:
wsServer = new Server(httpServer, {
...options,
cors: { origin: "*" },
pingInterval: 2000,
pingTimeout: 1000,
});
wsServer.listen(5000);
However, I prefer to use the same port as the HTTP server.
What is Namespace in Socket.io
Namespaces in Socket.io allow you to split a single Socket.io connection into multiple virtual connections. Each namespace acts as a separate endpoint, allowing you to organize your socket communication into separate categories or contexts. Namespaces have multiple advantages:
Namespaces are useful for creating multiple, isolated communication channels within a single application, each with its own set of clients and events.
Namespaces can be used to separate different components of your application, such as chat rooms, game rooms, or user-specific data.
and more …
In Socket.io, the default namespace is “/”, but you can easily create additional namespaces by appending a path to the URL when initializing the connection. The methods for emitting and listening to events are the same for both the default namespace and custom namespaces, allowing for a consistent and familiar API across your application.
Chat Namespace in Our App
I am using a different namespace, “/chat,” for the chat functionality in my application. This allows for better organization and decoupling of different real-time functionalities within the same server. Using namespaces instead of the default “/” namespace can also provide benefits in terms of scalability and deployment, as different namespaces with varying levels of traffic can be deployed on different infrastructure solutions as needed.
Note: It is not required to use a different namespace. e.g. “/chat”.
I have not added a lot of code in this namespace. It is a basic setup where I am only listening for new messages and logging them in the console. I will go into further detail in future articles.
import { Socket } from "socket.io";
import { IO, Namespace } from "../utils/ws.type";
export default (_: IO, __: Namespace) => (socket: Socket) => {
console.log("chat-namespace - socket connected", socket.id);
};
Setting up Everything
We have set up both the REST API and socket.io. Now let’s create an index.js file in the src directory and start both the REST and socket.io servers there.
import * as wsApi from "./ws";
import * as restApi from "./rest";
async function bootstrap() {
try {
const port = 8000;
const httpServer = restApi.listen(port);
wsApi.listen(httpServer);
} catch (err) {
console.log("App Bootstrap Error: ", err);
}
}
bootstrap();
So in the index.js file in the src directory, we will put all the necessary bootstrapping code that is needed for both the REST API and socket.io, such as database connections, etc.
Next Article
In the next article, I will explain the data model, including how to structure and save the data for our chat application in MongoDB. Stay tuned.
Please follow me on GitHub, Twitter and LinkedIn so that I can get more confidence and publish more articles in the future.
If you have any questions or concerns regarding the information provided above, or if you would like me to cover a specific topic in the future, please let me know in the comments section. If you want to support me, buy me a coffee here. Thanks for your time and support. Happy Coding.


