This is a side project that was stared in April of the year 2021. The main goal was to build a production-ready, error-resistant, fast, and highly scalable chatting server with support for groups, text channels, voice channels, and self-messaging. It was first developed as a monolithic architecture with Redis caching and its publisher/subscriber system integrated to establish communication between horizontally scaled server processes.
However, recently the whole codebaes is being revamped to move to microservice architecture with gRPC support for information exchange between independently running services. Also, here instead of using mongoose, Prisma is used to easily move to another DB (e.g. PostgreSQL) with minimal changes if required.
Currently, these are the services present in this architecture-
- auth - Responsible for authentication and
JWTtoken validation. All other services call this service to validate the JWT access token. Besides runninggRPCserver, it also provides a REST client for the end users.Prismais used to handleMongoDBdatabase connection and operations. - rest - Provides all the major REST APIs (CRUD APIs for groups, channels, self messages, etc.) for clients. For now,
mongooseis used forMongoDBoperations. - group - Exposes a public
RESTAPI to fetch group information, add members, add channels, and do all the group-specific activities. Also, there is agRPCserver that thesocketservice uses to retrieve the member lists of the group.Prismais used to handle database operations. Depends on theauthservice to checkJWTauthentication. - message - Provides
gRPCAPI for socket service to save new messages. Connected toauthservice throughgRPCto validate authorization. - socket - Responsible for opening
socketconnections between server and client. Authentication is required to establish the connection, hence usingauthservice throughgRPC. In a chat application, database reads for messages are significantly low compared to database writes.
There would be three ways to save messages in the database —
- The message is saved in the database through
RESTAPI asynchronously and also at the same time it is sent to the socket server. This workflow is terrible since there is no consistency at all. The message may not be saved in the database for some erroneous reasons but is successfully broadcasted to the recipients. Overall the UX would not be good at all. - First, call the
RESTAPI to save the message in the Database. If the response isOK, then send it to the socket server which will broadcast the message to all other group members or friends.
The problem here is the delay. The REST request may take some time because, for each message, it creates a costly TCP connection to a remote server placed somewhere in the world and the server then writes the message in the database and finally returns the response.
The whole process is costly. Imagine you are spamming in a group channel at your full speed and your browser ends up creating lots and lots of TCP REST requests. Message brokers can obviously be used but that helps the server side only.
- Another way would be to pass the message to the socket server which ultimately saves the message through a
gRPCcall to the message service. The benefit here is that the browser doesn’t need to create tons and tons ofTCPconnections to the server. There is only oneTCPconnection to the socket server andgRPCuses binaryprotobufprotocol for communication which is way faster than theRESTthing.
After the message is saved in the database, the socket server can pass the acknowledgment receipt back to the socket client and asynchronously broadcast the message to other socket clients. And again something like RabbitMQ can be used to prevent the Database from getting bombarded with write requests.
- Node.js
- Express
- Mongoose
- Prisma
- grpc
- Typescript
- JWT
- Socket.io
- Redis
- Bcrypt
- Docker
- NGINX
- Github Actions
- C++ (No use for now)
