Let's take a look at the top Node.js frameworks. Each framework has its own strengths and features, but which would be best for creating a simple, real-time chat application? Nowadays, web applications need such features to keep users engaged, so building a real-time chat app is necessary.
Node.js is a great tool for building real-time apps because of its asynchronous and event-driven design. However, even within the Node.js ecosystem, there are many frameworks to choose from. How do you pick the right one for your specific project? It's important to choose the correct Node.js framework for your chat app. Your choice can affect your project's progress, performance, and scalability.
The list of frameworks to be analyzed was taken from the results of the stateofjs 2022 survey.
We have selected the top 5 Backend Frameworks listed as the most used:
We list these requirements to make the problem more complex than a simple to-do. This helps us decide which Node.js frameworks are best for the solution. Then, we analyze each framework based on the information we gather.
Requirements:
Develop an application like a chat room that implements Real-Time Messaging. Users should be able to authenticate themselves. They should also create accounts, log in, and keep their identity during sessions. Messages should be delivered to the recipient in real-time without a manual refresh or page reload. The scope of these requirements does not include application deployment.
Express.js is a popular web application framework for Node.js. It's known for its simplicity and flexibility, making it a go-to choice for building web and mobile applications. Here's a brief overview:
I chose Angular to make the client and MongoDB for the database. Then, I searched for a library to connect the client and server in real time. Among the most common Real-Time Data Communication techniques are Server-Sent Events, Polling, and Web Sockets. I selected the latter for its bidirectional approach. The most used JavaScript libraries of the previously selected process are WS and Socket.IO. I chose the latter because its repository has more activity and more detailed documentation. I selected the Passport.js library because it is well above similar libraries such as everyauth, Grant, node-jsonwebtoken, CASL, etc., as indicated in its documentation: "Passport has a comprehensive set of over 480 authentication strategies covering social networking, enterprise integration, API services, and more".
In this library, I chose the passport-local method. It's a Passport strategy for logging in and registering new users with a username and password. I picked it because I wanted the server to have a bigger role in the process. Other strategies in the library, like passport-http-bearer, passport-facebook, passport-google-oauth, passport-auth0, etc., mainly rely on a third party for this responsibility. I successfully established communication between the client and server using the mentioned libraries. I didn't encounter any significant problems, so I was able to implement the solution to the problem. There's still room for improvement in the application, the code contains what is necessary to fulfill our desired scope.
Nest.js is a progressive Node.js framework for building efficient, reliable, and scalable server-side applications. It makes developing robust, maintainable web applications and APIs more straightforward by incorporating the best practices from other frameworks and languages.
First, I started reading the documentation and running the suggested basic project. The documentation is very well organized and complete, especially concerning WebSockets, Mongoose Integration, and Authorization.
I had not used this framework before, and my first experiment was to program a server using Angular. At first, I thought that this framework had a stricter architecture than Express. I considered making a folder for the UI layer and another for the business logic and database interaction, which includes controllers and services. However, the documentation recommends organizing each module with their own DTOs and Schemas. So, I decided to follow that approach. This framework is designed to be developed using TypeScript, so I chose to use it despite the challenges. The framework ensures that the style errors be fixed. Although it offers the option to select between any of the three package managers, NPM, Yarn, and pNPM, I opted for the classic NPM.
I implemented the WebSockets logic in the same order as Express. The official documentation says there are two supported WS platforms: socket.io and ws. I looked at the Socket.IO implementation in the documentation because I wanted it to be compatible with the Express project. In that project, I had already used the Socket.IO library for real-time communication with the Angular client. I wanted to use the same client for all the Frameworks on the list. I used the @nestjs/jwt library. Initially, I had in mind to use Passport.js for the authentication logic. However, the documentation presented a more framework-friendly approach using the @nestjs/jwt library, which is described as "JWT utilities module for Nest based on the jsonwebtoken package. This library includes generating and verifying JWT tokens". I used Passport.js in Express mainly for its authentication strategies and the integration as a middleware. Mostly to extract the credentials and the JWT tokens from the client requests. These can be easily handled in Nest by using the JwtModule and passing the credentials to the services that are injected as dependencies. As in Express, use the bcrypt library to hash the password when storing it in the database.
I established the communication flow between the client and server without any problems. I say this because evaluating usability is essential. This particular framework seems to stand out for that.
Fastify is a web framework for building efficient and high-performance web applications in Node.js. It is designed to be lightweight, extensible, and easy to use.
To begin with, I went through the general aspects of the documentation. I then initialized the project using the CLI and went through the structure of the resulting template. After that, within the documentation, I reviewed the three most important aspects of this project: WebSockets, MongoDB integration, and authentication.
Regarding WebSockets, Fastify offers two built-in options:
The fastify-ws library is not being actively developed. Additionally, the @fastify/websocket library does not offer a built-in method to broadcast messages. It seems necessary to iterate over the WebSocket Server clients to send the message to all clients connected to the conversation. To keep the code as simple as possible and maintain uniformity with previous implementations, I opted to use the external libraries 'fastify-socket.io' and 'socket.io.' With this, the code used was practically reused from the Express project.
The integration with MongoDB, as in the previous frameworks, was through the Mongoose library, which is integrated as in Express.
The authentication methods use the libraries: jsonwebtoken to verify the token's validity concerning the JWT Secret, generate the Access Token and the Refresh Token, and bcrypt to hash the passwords before storing them in the database. Something that becomes evident while solving the first errors is that the number of results from Google searches are less numerous than in Express and Nest. That's a clear indication that this community is still growing. I followed the best practices recommended in the official documentation. I used schemas, decorators, and plugins for this. Schemas helped me validate request objects and responses. The documentation mentioned that using a schema can increase throughput by 10-20%. I registered the fastify-socket.io library as a plugin. I also used decorators to add extra functionality to the reply object. This allowed me to pass user data from the authorization service to the controllers. I also used decorators to define the 'authorize' function. On the other hand, in the authentication process, the services are executed in the preHandler. The controllers in the handler, using the preHandler, are suggested when we need to access the body content in the authentication process.
Strapi is an open-source headless content management system (CMS) that provides a flexible and customizable way to manage and deliver content through APIs. It allows developers to build robust, scalable web applications and digital experiences.
Strapi is a tool for creating CMS and APIs quickly. At first, it didn't seem like a framework for making a Real-Time Chat application because its main focus is managing content and exposing it through APIs. But to evaluate it fairly, I looked at the documentation, especially the parts about WebSockets, MongoDB integration, and authentication.
The first thing I found was that, as stated in the documentation, "Starting from the release of Strapi v4, MongoDB is not supported natively anymore, and no connector is available". Because of compatibility issues between Strapi V4 and the socket.io library, I decided to downgrade to the latest release of V3, which is version "3.6.11". This version worked without any problems. I want to clarify that the reason I used different Node.js frameworks was to compare the client and database in a consistent manner. I also tried to use the socket.io library for WebSocket communication as much as possible.
The documentation is comprehensive, and I needed to consult doubts outside the official forums very few times. Unlike previous frameworks, Strapi offers an admin console in the browser for content management. Although the framework provides APIs to perform actions on users and collections, it is possible to create routes with custom logic to interact with plugins. I took advantage of this feature to define the Login and Signup routes to fit the format used in the Angular client. For authentication, I used the 'jsonwebtoken' library since I needed to customize the content of the tokens.
To connect to the MongoDB database, you just need to assign the right connector and other connection variables. Strapi has a query API to interact with the database. I used the 'socket.io' library to implement the WebSockets. The logic was the same as the previous frameworks. The only difference is how it's initialized. The logic for this library is placed in the bootstrap.js file. According to the documentation, the bootstrap.js file contains an asynchronous bootstrap function that runs before the application starts.
Finally, I could implement the proposed problem as in the previous frameworks. I believe that Strapi is not the appropriate framework for this type of application. That is to say that this type of application can be developed. But, all the functionalities the framework offers by default are being underused. The integrated logic could lead to unnecessary overhead when the application needs to be scaled.
Koa.js is a lightweight and modern web framework for Node.js, designed by the team behind Express.js. It was created to address some of the shortcomings of Express and to take advantage of newer features in JavaScript, especially the async/await syntax.
As in previous frameworks, I started reading the most relevant aspects of the documentation. From this point, the minimalism appears obvious since the documentation is clear but not extensive. The three most relevant aspects are WebSockets, integration with the MongoDB database and Authorization. I implemented the external libraries Socket.io, Mongoose, and jsonwebtoken, respectively. At this point, it is to be expected that the application has been implemented without significant problems.
Koa seems to be the most similar to Express of all the frameworks discussed so far. However, Koa has a few notable differences. One difference is that it allows us to manage Middlewares with a flow downstream and upstream. This means we can perform actions after the subsequent middleware has finished executing. We can also do post-processing of the response or additional operations based on previous results. Koa is helpful for tasks like timing requests, applying response transformations conditionally, or cleaning resources.
This pattern allows Koa to handle the "before" and "after" aspects of middleware logic in a single function. In the case of our Basic Real-Time Chat Application, I don't think middleware management will make a difference compared to Express. The use of modern JavaScript doesn't have a significant impact either. When comparing this framework to others for our specific problem, we can classify Koa as a more minimal version of Express with a smaller community. If we want to create new features and manage process flow more elegantly, Koa is an exciting option. Koa is flexible. So, you should prioritize organizing the structure and managing middleware, even for common tasks. Project components also need to be linked.