Understanding NestJS: Why It Exists, Its Core Foundation, and How Express.js Developers Can Learn It
Learn why NestJS was created, the architectural problems it solves, and how Express.js developers can transition smoothly to building scalable backend applications using dependency injection, modules, controllers, and modern TypeScript patterns.
Written byMonir Hossain
Oct 6, 2026
13 min read
If you've built a few APIs with Express.js, you probably know the feeling.
The first week is bliss. You run npm init, install Express, write a few routes, and suddenly you have a working API. It's fast, flexible, and deliberately unopinionated.
Then six months pass.
You have 150 routes, dozens of middlewares, services and utilities scattered across the project, and every new feature feels like performing surgery on an already complicated system.
That exact problem is one of the reasons NestJS exists.
If you're an Express.js developer looking at NestJS and thinking:
"Isn't this just over-engineered Express?"
This post is for you.
We'll look at why NestJS exists, what it actually adds on top of Express, how its core architecture works, and how an Express developer can learn it without memorizing dozens of decorators.
Who Is This Post For?
This is a practical guide for developers who already understand Express.js and want to learn NestJS at an architectural level, rather than simply memorizing decorators.
You should already be comfortable with concepts such as:
The goal is to help you understand why NestJS is designed the way it is.
1. What Is NestJS Really?
NestJS is a progressive Node.js framework for building efficient, reliable, and scalable server-side applications.
It was created by Kamil Myśliwiec in 2017 and was heavily inspired by Angular's architectural approach.
At its core, NestJS gives Node.js developers something that Express intentionally doesn't provide:
A structured architecture for organizing large applications.
Why Did NestJS Become Popular?
There are several reasons.
TypeScript First
NestJS was designed with TypeScript in mind from the beginning.
It makes heavy use of:
Classes
Interfaces
Decorators
Dependency Injection
Metadata
Strong typing
Enterprise-Friendly Architecture
As applications grow, architecture becomes increasingly important.
A small Express application can be beautifully simple. But as the application grows to hundreds of routes and multiple developers, teams need predictable rules for:
Where business logic belongs
How dependencies are managed
How authentication is implemented
Where validation happens
How modules communicate
How applications are tested
NestJS provides conventions for these problems.
Batteries Included
NestJS provides official integrations and patterns for many common backend requirements, including:
Validation
Configuration
Authentication
Authorization
Testing
WebSockets
GraphQL
Microservices
Logging
Queues
You don't have to use all of them, but the framework provides a consistent way to integrate them.
2. NestJS vs Express.js
The most important thing to understand is this:
NestJS is not simply a replacement for Express.
Express is primarily an HTTP framework and routing library.
It gives you tools such as:
req
res
next()
and then largely gets out of your way.
NestJS is an application framework.
It provides:
Modules
Dependency Injection
Controllers
Providers
Pipes
Guards
Interceptors
Exception Filters
A structured application lifecycle
By default, NestJS uses Express as its underlying HTTP adapter. It can also use Fastify.
That said, using raw Request and Response everywhere defeats many of the benefits NestJS provides. In most cases, NestJS's decorators and return-value-based response handling are preferable.
3. Common Misconceptions About NestJS
"NestJS is slower than Express."
NestJS does introduce additional abstraction compared with using Express directly.
However, the practical performance impact depends heavily on the application.
In most real-world APIs, database queries, external API calls, network latency, serialization, and other I/O operations are much more significant than the framework abstraction itself.
If raw HTTP throughput is your primary concern, NestJS can also run on Fastify.
"NestJS is only for large teams."
Not necessarily.
You can build a small CRUD API with NestJS.
The main advantage is that the architectural conventions you establish early can continue to work as the application grows.
The trade-off is that NestJS has more structure and therefore more concepts than a minimal Express application.
"I need to learn Angular first."
No.
NestJS borrows architectural ideas from Angular, but you don't need Angular experience to learn NestJS.
If you're already comfortable with:
TypeScript classes
Interfaces
Modules
REST APIs
you already have much of the foundation you need.
4. The Problem With Large Express Applications
Let's look at the problem NestJS is trying to solve.
The Honeymoon Phase
An Express application can start incredibly clean.
A controller doesn't have to literally be under 10 lines. The important principle is that it should remain focused on transport-level concerns, not become a second service layer.
10. Providers and Services
Providers are classes that NestJS can manage through its dependency injection system.
A service is one of the most common types of provider.
The service doesn't care whether it is called from:
REST
GraphQL
WebSockets
A background job
A CLI command
Its responsibility is application/business logic.
That's an important separation.
11. Decorators: The Syntax That Makes NestJS Look Different
If you've never used NestJS, decorators can initially look intimidating.
But you don't need to understand every decorator immediately.
At a basic level, decorators attach metadata to classes, methods, and parameters so that NestJS can understand how they should behave.
For example:
NestJSPurposeExpress Equivalent@Module()Defines module metadataFolder/architecture convention@Controller('users')Defines controller route prefixexpress.Router()@Injectable()Marks a class as a providerManual dependency management@Get()Defines GET endpointrouter.get()@Post()Defines POST endpointrouter.post()@Body()Extracts request bodyreq.body@Param()Extracts route parametersreq.params
This graph allows NestJS to determine how providers should be resolved.
Circular dependencies can still exist in NestJS, and the framework provides mechanisms such as forwardRef() for certain cases. The important point is that NestJS makes dependency relationships explicit rather than leaving everything to manual module imports.
Step 4: Provider Resolution
NestJS resolves providers according to their configured scope and dependencies.
For example:
PrismaService
↓
UsersService
↓
UsersController
NestJS ensures that the required dependencies are available when these components are instantiated.
Step 5: Route Registration
NestJS reads metadata from decorators such as:
@Controller('users')
@Get()
@Post()
@Get(':id')
and registers the corresponding routes with the underlying HTTP adapter.
Conceptually:
GET /users
↓
UsersController.findAll()
GET /users/:id
↓
UsersController.findOne()
POST /users
↓
UsersController.create()
13. The NestJS Request Lifecycle
One of the most useful concepts for an Express developer is understanding how a request flows through NestJS.
Once you understand this lifecycle, NestJS becomes much less mysterious.
14. The Mental Model Shift for Express Developers
The biggest challenge when learning NestJS isn't syntax.
It's changing the way you think about application architecture.
Express MindsetNestJS MindsetI write functionsI organize responsibilities into classesI manually create dependenciesI declare dependenciesRoutes are registered manuallyRoutes are defined in controllersreq / res everywhereDTOs and return valuesMiddleware handles many concernsSpecialized lifecycle componentsFiles are often grouped by typeFeatures are grouped into modulesDependencies are manually importedDependencies can be injected
The goal isn't to stop thinking like an Express developer.
It's to add another architectural layer to the way you think about backend applications.
15. How to Learn NestJS Quickly If You Know Express
Don't start by trying to learn every NestJS feature.
Instead, rebuild something you already understand.
Week 1: Foundations
Build a simple CRUD application.
For example:
Users
├── Create user
├── Get users
├── Get user
├── Update user
└── Delete user
Learn these concepts first:
Modules
Controllers
Providers
Dependency Injection
DTOs
Pipes
Exception handling
Start With DTO Validation
Instead of manually validating request bodies:
if (!email) {
// ...
}
use DTOs and validation.
For example:
import {
IsEmail,
IsString,
MinLength,
} from 'class-validator';
export class CreateUserDto {
@IsEmail()
email: string;
@IsString()
@MinLength(8)
password: string;
}
Then configure a global validation pipe:
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
}),
);
Now validation becomes part of the framework's request lifecycle instead of being repeated manually in every controller.
16. Week 2: Production Patterns
Once the fundamentals make sense, move into production-oriented concepts.
Learn:
Configuration
Use ConfigModule for environment-based configuration.
Exception Filters
Create consistent error responses.
Guards
Implement authentication and authorization.
Interceptors
Handle cross-cutting concerns such as:
Response transformation
Logging
Metrics
Caching
Database Integration
Learn how to integrate your preferred ORM or database layer, such as Prisma.
Testing
Understand:
Unit tests
Integration tests
End-to-end tests
17. Performance and Scalability Considerations
NestJS is designed to support scalable applications, but you still need to understand the trade-offs.
Express vs Fastify
NestJS can run on different HTTP adapters.
If your application requires very high HTTP throughput, Fastify is an option:
const app = await NestFactory.create(
AppModule,
new FastifyAdapter(),
);
The exact performance difference depends on your workload, application architecture, serialization, database access, and other factors.
Don't switch adapters simply because "Fastify is faster." Benchmark your actual workload.
Avoid Request-Scoped Providers Unless Needed
NestJS providers are singleton-scoped by default.
Request-scoped providers can be useful when you genuinely need request-specific state, but they introduce additional lifecycle overhead.
Use them intentionally rather than by default.
Caching
NestJS provides abstractions that can help structure caching.
For distributed applications, Redis is commonly used as the backing store.
But caching should be introduced based on an actual performance requirement rather than simply because the framework supports it.
18. A Common Beginner Error
One error you'll probably encounter is:
Nest can't resolve dependencies of the UsersService
NestJS can only inject providers that are properly registered and visible within the module graph.
Once you understand modules and provider visibility, these errors become much easier to diagnose.
19. When Should You Use Express Instead?
NestJS isn't automatically better than Express.
Express remains an excellent choice when you want:
A small API
Minimal abstraction
Maximum flexibility
A quick prototype
A simple internal service
Full control over application structure
For a small project, NestJS's additional structure may even feel unnecessary.
20. When Does NestJS Make More Sense?
NestJS becomes particularly attractive when your application has:
Multiple domains
Many developers
Complex business logic
Authentication and authorization
Multiple integrations
Background processing
Extensive validation
Large numbers of endpoints
Long-term maintenance requirements
The key benefit isn't that NestJS makes individual endpoints dramatically easier to write.
The benefit is that it makes large applications more predictable to build and maintain.
21. The Bigger Picture
The most important lesson isn't:
"NestJS uses decorators."
It's:
NestJS gives Node.js applications an architectural system.
Express gives you freedom.
NestJS gives you conventions.
Express asks:
"How do you want to organize this application?"
NestJS says:
"Here are proven patterns you can use to organize it."
Neither approach is universally better.
The right choice depends on the size, complexity, team, and lifetime of the application.
Conclusion
Express taught an entire generation of Node.js developers how simple it can be to build APIs.
NestJS takes that foundation and adds a structured application architecture on top of it.
The biggest things to understand are:
Modules define feature boundaries.
Controllers handle HTTP concerns.
Providers contain application logic and dependencies.
Dependency Injection manages how components interact.
Pipes handle validation and transformation.
Guards handle authorization and authentication.
Interceptors handle cross-cutting behavior.
Exception Filters provide structured error handling.
You don't need to abandon Express to learn NestJS.
In fact, knowing Express first gives you an advantage because you already understand what's happening underneath the abstraction.
Your next step is simple:
Take one of your existing Express routes and rebuild it as a NestJS feature module.
Create the module.
Move the HTTP logic into a controller.
Move the business logic into a service.
Inject the dependencies.
Add DTO validation.
Then compare the two implementations.
That's when NestJS starts to make sense—not because you've memorized its decorators, but because you understand the architectural problem it is trying to solve.
Built with NestJS, TypeScript, Prisma, and lessons learned from building and maintaining Express applications that grew beyond their original scope.
#NestJS
#ExpressJS
#TypeScript
#Backend Development
#Dependency Injection
#REST API
#Web Development
#Backend Engineering
MH
About the author
Monir Hossain
Full-stack developer from Bangladesh, focused on building modern web applications, backend systems, and practical developer experiences.