We've been playing with different JS frameworks but with Nest.js we could do more in far less time and it was more convinient.
About a year ago, we started the development of a contact center application for one of our clients. One of the key requirements of this app was the ability to provide real-time messaging between the customers and consultants. We had a team of full-stack developers who were experienced in building software using MERN stack. We were in a good position because Node.js works very well with technologies providing efficient real-time communication between the client and the server, like WebSockets, which we could use in this project.
Various modules that are available on NPM work well with TypeScript. In reality, typings provided by the community are not perfect and you often have to wait for the new version of typings before you upgrade some of your dependencies. This can take a while, particularly for less popular modules. For those reasons, we wanted a framework which was written in TypeScript. This is the case of Nest.js. Nest.js has put decorators (one of the experimental features of TS) to good use and uses reflection API (for example when doing dependency injection or generating API documentation), which allowed us to write very concise and readable code.
Feature-rich, but modular
Our main objection against using Express without a framework was its lack of commonly used features such as dependency injection, parameter validation, logging and so on. Of course, you could incorporate them by plugging in various middlewares, but it’s very handy to have these basic features available without writing any glue code. This is one of the main reasons why we use frameworks instead of libraries. On the other hand, frameworks are very often bloated with features that you don’t need. I think that Nest.js has found a sweet spot between feature richness and modularity. Basic features are there ready to use right away and more advanced, or less commonly-used functions are available as separate packages that you install from NPM and import in your project.
Built-in dependency injection
We mostly write object-oriented code and also use architectural patterns like Clean Architecture (aka. Hexagonal Architecture) and tactical Domain-Driven Design. One of the main concepts of these architectures is to separate the interface and its implementation. In order to do this, the code can’t import the implementation – it can only import the interface and implementation has to be injected. This is why we needed the support for dependency injection which allowed us to do a dependency inversion. Whether it’s true or not that you can incorporate a DI tool like Awilix into any framework, Nest.js has it built-in and is very flexible. We can inject classes, functions and values with different scopes like Singleton or Request.
Support for event-driven architecture
Our project is not entirely event-driven but we often use events to loosen the coupling between pieces of code that should not know about each other, yet still need to communicate. Even though the in-memory event bus is not hard to implement in Node.js, with Nest.js we get ready to use the solution provided by cqrs module. It has many features but it is easy to use only one. In our case, the event bus was backed by rxjs and supporting typed events.
Help in writing documentation
In our project, we only had private API used by our frontends but the need for good documentation is as big as with public API. We wanted to use a well-known standard to describe our endpoints and we also wanted our documents to always be up to date. Nest.js provides the Swagger module, which does the following:
- Allows us to write documentation using decorators in code near the endpoint they describe, thus guaranteeing their freshness. When you change code you also have to update the decorator.
- Gets auto-generated interactive frontend with all endpoints of our API that is always up to date.
- Exports documentation in OpenAPI (aka. Swagger) format, which is the de facto standard of documenting API in the whole IT industry.
When you work on a project as a team it is crucial to define the standard ways of doing certain things. For example, defining a new endpoint, documenting it, adding authorization, defining response codes, handling errors, injecting dependencies and so on. We as developers stick to these standards which results in our codebase being concise and structured. One of the main purposes of using a framework is to have these standards defined by its creators. Nest.js has many common, standard concepts like modules, controllers, authentication guards, error interceptors and so on expressed as framework features. Just by using a framework we automatically enforce these standards in a project. Moreover, Nest.js extensively uses decorators (such as when defining route paths or response codes of API endpoints). This results in a very declarative code which is easier to understand than imperative code.
Stable development and a supportive community
Last but not least, we wanted to use a tool that is actively maintained and has the support of a great community of IT companies that are also using it in their projects. Although Nest.js is quite young (only just two years old) it’s very stable and we’ve never encountered any critical bug that stopped our development. Even upgrades between major versions of Nest.js were very straightforward and easy.
Was Nest.js the only option?
In my opinion, most situations in which the technology used is not crucial for the success of a project – the achievement is down to the people working on it. I’m sure we would have built a great product using a different framework, or even pure Express. With Nest.js we could do more in far less time, all whilst having great fun. This is a win-win situation for both business (who save money) and developers (who are happier) and ensures that we don’t regret this decision.