Why NestJS is My Go-To Framework

Why NestJS is My Go-To Framework

CONTENFUL
NestJS is great. This is my very non-technical approach to explaining why I love working with NestJS for backend app development. This is also an introduction to the more technical and example-rich series of practical developments using NestJS.

How it started — for me

In 2016, after several years of JavaScript development, I realized I needed a more structured and scalable approach to my tech stack. At the time, TypeScript was gaining traction, offering the type safety and maintainability that JavaScript lacked. Seeing the benefits of static typing, better tooling, and improved developer experience, I made the transition. This shift allowed me to build more scalable and maintainable applications while still leveraging the flexibility of the JavaScript ecosystem.

The cold shower of JavaScript

Learning new tools becomes easier over time. The more documentation you absorb, the more frameworks and architectural patterns you explore, the more you start seeing commonalities and best practices. But back in 2016, I was deeply embedded in JavaScript development, building projects with Node.js, Express, and Firebase. I had a solid grasp of modular backend design and knew how to structure APIs efficiently.
So naturally, when I decided to modernize my approach, my initial thought was simple: I would do everything I did before—just in TypeScript. I was wrong.
Writing applications in JavaScript often felt like a free-for-all—no clear conventions, no guardrails, just endless ways to structure an application. Switching to TypeScript didn’t just feel like a syntax upgrade, it was a fundamental shift in how I approached architecture. I quickly realized how much I had taken for granted in statically typed languages. Suddenly, enforcing SOLID principles, TDD, DI, and structured ORM usage wasn’t as straightforward as before.

Finding Structure — A Better Stack

I knew I needed a new approach. My goal was simple: regain the structure and best practices I had lost in my transition. I started adding tools to my stack:
  • Jest → for testing
  • TypeScript → for type safety and better DX
  • TypeORM → for database integration
  • Dependency Injection (DI) → to support clean architecture and TDD
One of the first challenges was finding the right DI container. That led me to Inversify.js, which offered a cleaner way to handle dependencies and improve testability. But something was still missing—a framework that tied everything together seamlessly.
It was around this time that a colleague suggested NestJS as an alternative. At first, I was hesitant, but as soon as I dug into its modular architecture, built-in DI, and first-class TypeScript support, I knew this was exactly what I had been looking for.
NestJS didn’t just bring back structure, it introduced a new level of scalability and maintainability that transformed how I built applications. It was a game-changer, and to this day, it remains my go-to framework whenever I need to build a robust and scalable backend.
Looking back, my journey from JavaScript to TypeScript wasn’t just about switching languages—it was about rethinking how I build software. Moving to TypeScript and NestJS gave me the best of both worlds: the flexibility of JavaScript with the scalability of strongly-typed, structured applications.
Today, whenever I start a new project, I default to NestJS—not just because it’s the best tool, but because it represents everything I learned through this transition.

But why is NestJS so good?

First of all, it’s opinionated which I think is a good thing. When you are working out a generic approach to solving problems, you don’t want to waste time reinventing the wheel over and over again.
The structure of any app you will be developing with NestJS will be distributed into individual modules.
Modules might have dependencies between each other and those will be resolved by dependency injection. My personal preference is to define interfaces for the dependencies that will be used across modules, which is compliant with the Inversion of Control (IoC) principle.
Modules usually consist of their definitions, providers, and if needed a controller but they can contain more complex structures such as pipes, guards, interceptors, exception filters, or simply middlewares.
Provider is a generic name for a class that will support some specific function in the app. Here, I think the documentation describes it best:
Providers are a fundamental concept in Nest. Many of the basic Nest classes may be treated as a provider — services, repositories, factories, helpers, and so on. The main idea of a provider is that it can be injected as a dependency; this means objects can create various relationships with each other, and the function of “wiring up” these objects can largely be delegated to the Nest runtime system.
In blog posts that will follow in this series, I will be explaining my practical use-cases for each of these structures so I will not focus on them too much right now but the important takeaway here is that NestJS has solved many design problems by offering ready-to-use solutions for them, and at the same time, it doesn’t force you to follow these solutions, which is nice.

Decorators

In most cases, when building apps with NestJS and deciding which class will be responsible for what is achieved, this is done through the usage of decorators. For example, the @Injectable decorator will tell NestJS that the class can participate in the DI flow. The @Controller decorator will define a controller that will automatically be bootstrapped into application routing.
I will be focusing on the usage of decorators and creating custom decorators in some of the next articles.

Types

NestJS works well with TypeScript and will encourage you to define types across your app. It will also provide a simple way for type validation when it comes to for example DTO and requests to your API routes. Defining types will make it easier to develop and navigate the codebase but also will help to generate living documentation for your API if you need one.

Database support

There is a huge support for usage of TypeORM and I don’t write this section after the Types section by coincidence. For me, it’s crucial to be able to interact with the Persistence Layer using strictly defined interfaces. Implementation in NestJS usage of TypeORM is very intuitive and helps a lot to support the relation of the code and database. It is very important to be sure what will come out as a result of a database query and what will be the representation of an entity object resolved from it.
In NestJS + TypeORM, the schema of the database table is represented by the Entity class, which properties are decorated with specific TypeORM decorators, describing what is a specific type, relation, or structure.
Keeping the sync between the codebase describing the persistence layer is a huge subject alone which I will be getting into in upcoming articles, but for now, I will just say that NestJS and TypeORM propose at least two strategies that will be suitable for any stage of the development of your app, from the local development environment to production environment.

NestJS CLI

Another utility that I find very useful is the CLI of NestJS, which will mostly help out with generating various components of your app. You will most likely use it right away when you get started with this framework. It will generate a new project with a helpful wizard. But this is not the last time you will be using the nest command.
Each time you create a new module or add a provider to a module you can use the CLI for it, which really saves some time but also makes sure you follow a specific pattern and code structure. The most popular command I use is the one for generating a nest CRUD resource module, which, with one command, will output a code skeleton for a module, controller (in case of REST), service, entity, DTO classes, and even spec tests in Jest. Not only that, it will generate basic CRUD methods in the controller of your newly created module and their respective service methods.
I’ve mentioned generating CRUD for REST but there are many other options the generator offers, such as GraphQL, WebSockets, or the Microservice (Non HTTP). All of which I will cover in upcoming articles and their practical use cases on a live project.

Quality Assurance

I’ve already mentioned that NestJS CLI is capable of generating tests for newly generated providers. To be specific it outputs the skeleton spec.ts which will be addressed to testing the class you’ve just created. What is important to note — before I get deep into testing with NestJS in another article — is the fact that NestJS provides a testing module. It is significantly important and related to the DI principle since I can decide on what level of responsibility I am willing to test.
With the structure of my app, which is distributed into modules that contain their providers, I can focus on testing even the single method of the class, even if it is entangled with a complicated dependency schema. NestJS allows me to create a Testing Module to which I can almost dynamically define its providers, without the need to bootstrap the full app for testing, but at the same time not changing its behavior. It might sound trivial for those who are not bothered with testing their apps; but in any standard app, you might find yourself in a situation where you would perhaps like to test a simple class method that performs some business logic, but you are forced to provide the dependency chain for everything the class containing this method requires.
In the end, you end up spending hours resolving complicated dependency requirements or sometimes even infrastructure dependencies such as databases. With NestJS’ Testing Module, it is way simpler and you decide what is the required dependency scope you would like to involve. I will provide specific examples of various testing techniques in the next articles.
Having that said, it’s still very possible and easy to create a standalone app inside the test, running a complete app with all of its functionalities to perform E2E tests.
There is also a third option which I tend to use whenever I deal with the Repository Pattern, where I could provision a live database that would be capable of running actual DB queries to make sure those are returning correct results.

Conclusion

Those are just some of the things I use each day in my professional career. Trust me there are many more. The documentation is great and I tend to go back to it on various occasions to see its frequently updated versions.
This article is an introduction to what will be a series of articles about NestJS development in real-life scenarios. I will be showing practical use-cases that I’ve found in many projects I have been a part of over the past years. I will focus on the complete lifecycle of product development, from specification and design to deployment to the target infrastructure. I will be focusing on REST API development but I will mention other techniques.