Skip to main content

Command Palette

Search for a command to run...

What is Inversion Of Control?

Published
5 min readView as Markdown
What is Inversion Of Control?
H

hasabTech is a developer-focused educational platform committed to simplifying tech learning. We share practical coding tutorials, programming guides, and tech insights to help beginners and aspiring developers build real world skills. Our mission is to make technical education accessible, clear, and community driven. We are currently building our in house products, experimenting with real world tech solutions, and sharing what we learn along the way.

Introduction

In our previous blog we explored in detail how to implement Dependency Injection and understood its role in creating loosely coupled and more maintainable code. As we discovered there are multiple ways to implement Dependency Injection, something we will delve into in future blogs.

In this blog, our focus shifts to a closely related but often misunderstood concept: “Inversion of Control”.

What is Inversion of Control?

Inversion of Control is a design principle, not a design pattern and this distinction is crucial. While the terms are sometimes used interchangeably they represent different aspects of software design.
In traditional programming, your classes or modules are in charge of creating and managing their own dependencies. However with Inversion of control is inverted an external entity, such as a framework or container takes over the responsibility of providing those dependencies. This shift of responsibility leads to better code reusability, testing, and decoupling.

Design Principles vs. Design Patterns

Let’s pause for a moment to clear up a common confusion: the difference between design principles and design patterns.

A design principle is a high-level guideline or best practice that helps developers design robust software systems. It is conceptual and theoretical, focusing on how to approach a problem.

A design pattern, on the other hand, is a low-level, practical solution to a recurring programming problem. It provides a specific structure or template that you can use to solve implementation challenges.

In simpler terms:

Design principles guide how to think, while design patterns show how to build.

A real life example

To understand the concept of dependency, let’s consider a few simple real-world examples.

In everyday language, the word "dependency" means something you rely on. For instance, you depend on your laptop to do your work — this makes the laptop your dependency.

Now let’s take the example of a car. A car depends on many components to function — such as an engine, wheels, doors, windows, and more. Among them, the engine is a crucial part. So, we can say:

The engine is a dependency of the car.

In object-oriented programming, if you're building a Car class, you’ll need to create objects for each of these components. That means you are manually managing the dependencies inside your class. This tight coupling makes your code complex and harder to modify.

For example, if you want to:

  • change the engine type

  • update the tire size,

  • adjust the window color,

  • replace the car's color

you’d need to modify the Car class directly. This results in a tightly coupled system where every small change requires direct modifications violating good software design practices.

The goal in modern software engineering is to build loosely coupled systems and that’s where the concept of Inversion of Control comes in.

In the traditional approach, the code controls the creation of dependencies, but Inversion of Control shifts this responsibility away from your class and hands it over to an external entity in our case, the NestJS framework.

So instead of manually creating and managing all the dependencies inside the Car class (or any other class), you delegate that control to the framework.

This concept of transferring control of dependency management from your code to the framework is called Inversion of Control, and in NestJS, it is achieved through Dependency Injection.

Applying Inversion of Control in NestJS (with Dependency Injection)

Let’s look at how NestJS handles Inversion of Control using Dependency Injection with a simple example.
CatsController

import { injectable } from '@nestjs/common';

@injectable()
import class CatService {
    private readonly cats: string [] = []; 

    create(cat:string) {
        this.cats.push.(cat);
    }
    findAll() : string [] {
        return this.cats;
    }
}

Here we define a CatsService and mark it with the @Injectable() decorator. This tells NestJS that this service can be injected as a dependency into other components like controllers.


import { Controller, Post, Body, Get, } from '@nestjs/common';
import { CatsService } from './cats.service';

@Controller('cats')
export class CatsController {
    constructor (private readonly catsService: CatsService) }


@Post()
async create (@Body() cat:any){
    console.log('cat', cat);
    this.catsService.create(cat);
}
@Get()
async findAll() : Promise<any[]>{
    return this.catsService.findAll();
}

In the controller, we inject the CatsService through the constructor. This is a clear example of Dependency Injection rather than creating a new instance of the service manually, NestJS provides it automatically.

By using the @Injectable() and constructor pattern, NestJS manages the lifecycle and creation of the CatsService for us.

This is a real world example of Inversion of Control we are no longer manually controlling how the service is created or used. Instead, NestJS takes over that control.

This makes our code more modular, testable, and maintainable.

Registering Dependencies in the Module

In NestJS, you cannot inject a class (like a service) into a controller unless it’s registered as a provider within a module. This registration allows NestJS to manage the class through its Inversion of Control (IoC) container.

Let’s look at the module definition:

@Module({
    controllers : [CatsController],
    provider : [CatsService],
})
export class AppModule {}

In the AppModule, we list CatsService under the providers array.

By doing this, we are telling NestJS to create and manage the instance of CatsService within its IoC container.

Once registered, CatsService becomes available for injection into other parts of the application like the CatsController.

Conclusion

In this blog, we explored the concept of Inversion of Control (IoC) and how it's implemented in NestJS using Dependency Injection (DI).

We began by understanding what dependency means using simple real-life examples and explained how managing dependencies manually can lead to tightly coupled and hard-to-maintain code.

Inversion of Control helps solve this by shifting the responsibility of dependency management from your code to the framework. In NestJS, this is achieved through Dependency Injection, a design pattern that allows the framework to supply required services (or other dependencies) into your components.

We also learned that in order to inject any service into a controller, it must first be registered as a provider in a module. Once registered, NestJS uses its built-in IoC container to create and manage instances of that service behind the scenes.

If you’re a visual learner, check out the following video tutorial for this blog:

https://www.youtube.com/watch?v=U0LGQjxyuw8

Follow us for more such content:

https://www.linkedin.com/company/hasabtech

https://www.youtube.com/@hasabTech

https://www.instagram.com/hasab.tech/

https://www.facebook.com/hasabTech