# Dependency Injection in NestJs for beginners

## Introduction

Hey there, fellow developers! Today, I want to share with you a powerful technique that can take your NestJS applications to a whole new level. It's called dependency injection (DI), and trust me, once you get the hang of it, it'll become your secret weapon for writing clean, maintainable, and modular code. So buckle up and get ready to dive into the wonderful world of dependency injection in NestJS!

## **Unleashing the Power of Dependency Injection**

Okay, let's break it down. Dependency injection is like having a super-smart assistant who automatically handles all the nitty-gritty stuff for you. It's a software design pattern that lets you decouple components from their dependencies, making your code more flexible and scalable.

*Picture this*: you're building an awesome NestJS app that sends SMS notifications to users. But hey, what if you want to switch from one SMS provider to another without breaking a sweat? That's where DI comes in to save the day! With dependency injection, you can seamlessly swap out providers just by updating the module where it's injected. No more headaches or messy code changes. How cool is that? - We will get to this example as soon as we knock down the constructor injection which is one of the techniques that powers dependency injection.

## **Simplifying Your Life with Constructor Injection**

Now, let's talk about constructor injection, a key aspect of DI. Imagine you have a complex service in your app that requires multiple dependencies to function properly. Without dependency injection, you would have to manually create and manage instances of each dependency within the service. It can quickly become a nightmare, especially when the number of dependencies increases.

But fear not! Dependency injection comes to the rescue. With constructor injection, you can simply declare the dependencies as constructor parameters, and NestJS injects the required instances for you. It's like having a magical genie fulfilling your service's every wish.

Imagine we have a `ComplexService` that needs three dependencies: `DependencyA`, `DependencyB`, and `DependencyC`. Now, let's say `DependencyA` itself requires some constructor values. With dependency injection, you don't need to worry about providing those values explicitly. Here's an example:

```typescript
import { Injectable } from '@nestjs/common';
import { DependencyA } from './dependency-a';
import { DependencyB } from './dependency-b';
import { DependencyC } from './dependency-c';

@Injectable()
export class ComplexService {
  constructor(
    private readonly dependencyA: DependencyA,
    private readonly dependencyB: DependencyB,
    private readonly dependencyC: DependencyC,
  ) {
    // Constructor logic goes here
  }

  // Service methods and functionality
}
```

In this example, even if `DependencyA` requires some constructor values, you don't need to pass them explicitly when creating an instance of `ComplexService`. NestJS will automatically handle the injection of all dependencies, including the required constructor values of `DependencyA`.

What If There's No Dependency Injection?

Now, imagine a scenario where you don't use dependency injection. Without DI, you would have to manually create instances of each dependency within the service. Your code might look like this:

```typescript
import { Injectable } from '@nestjs/common';
import { DependencyA } from './dependency-a';
import { DependencyB } from './dependency-b';
import { DependencyC } from './dependency-c';

@Injectable()
export class ComplexService {
  private readonly dependencyA: DependencyA;
  private readonly dependencyB: DependencyB;
  private readonly dependencyC: DependencyC;

  constructor() {
    this.dependencyA = new DependencyA(/* Constructor values for DependencyA */);
    this.dependencyB = new DependencyB();
    this.dependencyC = new DependencyC();
    // Constructor logic goes here
  }

  // Service methods and functionality
}
```

As you can see, without dependency injection, you need to manually create and manage instances of each dependency, including providing the required constructor values. This can quickly become cumbersome and error-prone, especially as your application grows in complexity.

## Switching Providers (Dependency Service) with Ease

Let's dive into a practical example to understand how dependency injection can make our lives easier.

Imagine we have an `SmsService` responsible for sending SMS notifications to users, and it needs a way to interact with different SMS providers, such as Twilio or Nexmo.

To achieve this flexibility, we use a concept called dependency injection. It allows us to decouple the `SmsService` from any specific SMS provider implementation. But how does it work?

In our scenario, we define an interface called `ISmsProvider`. An interface is like a contract that specifies a set of methods that a class must implement. In our case, the `ISmsProvider` interface defines a method called `sendSMS` that takes in a phone number and a message and returns a boolean indicating whether the SMS was successfully sent.

Here's an example of how the `ISmsProvider` interface could look:

```typescript
interface ISmsProvider {
  sendSMS(phoneNumber: string, message: string): boolean;
}
```

By defining this interface, we're essentially saying that any class that wants to be an SMS provider must implement the `sendSMS` method with the same parameters and return type.

Now, let's move on to the `SmsService`. Instead of directly using a specific provider like Twilio or Nexmo, we declare a constructor parameter of type `ISmsProvider` in the `SmsService` class. This means that the `SmsService` expects an object that conforms to the `ISmsProvider` interface to be injected when creating an instance of the `SmsService`.

By injecting the `ISmsProvider` through the constructor, we create a loose coupling between the `SmsService` and the actual SMS provider implementation. This allows us to switch providers easily without modifying the core logic of the `SmsService`.

Let's see an example:

```typescript
class SmsService {
  constructor(private readonly smsProvider: ISmsProvider) {}

  sendSMS(phoneNumber: string, message: string): boolean {
    return this.smsProvider.sendSMS(phoneNumber, message);
  }
}
```

In this example, the `SmsService` class has a method called `sendSMS` that takes a phone number and a message as parameters. It uses the injected `smsProvider` object to call the `sendSMS` method on the provider, abstracting away the specific implementation details.

Here's an example of the `TwilioSmsProvider`:

```typescript
import { Injectable } from '@nestjs/common';
import { ISmsProvider } from './sms.provider.interface';

@Injectable()
export class TwilioSmsProvider implements ISmsProvider {
  sendSMS(phoneNumber: string, message: string): Promise<boolean> {
    // Implement the Twilio SMS sending logic here
  }
}
```

To switch providers, all we need to do is update the module configuration where the `SmsService` is injected. Here's an example:

```typescript
import { Module } from '@nestjs/common';
import { SmsService } from './sms.service';
import { ISmsProvider } from './sms.provider.interface';
import { TwilioSmsProvider } from './twilio-sms.provider';

@Module({
  providers: [
    {
      provide: ISmsProvider,
      useClass: TwilioSmsProvider,
    },
    SmsService,
  ],
})
export class SmsModule {}
```

In the `SmsModule` configuration, we define that the `ISmsProvider` should be provided by the `TwilioSmsProvider` class. By simply updating this configuration, we can seamlessly switch to using Twilio as our SMS provider.

The beauty of dependency injection is that we don't need to touch the `SmsService` implementation. It remains blissfully unaware of the underlying provider implementation. This decoupling allows us to easily switch providers, improve flexibility, and keep our codebase clean and modular.

## **Testability? You Bet!**

But wait, there's more! Dependency injection also supercharges the testability of your code. By injecting mock or stub implementations of dependencies during unit tests, you can isolate your components and ensure reliable and thorough testing. No more tangled web of dependencies ruining your testing party. It's all about that sweet, sweet isolation!

## **Conclusion**

Congratulations, my fellow developer! You're now armed with the knowledge of dependency injection in NestJS. With its modularity, scalability, and testability superpowers, you can build remarkable applications that are a breeze to maintain and extend.

So, don't be shy—embrace dependency injection, unlock the full potential of NestJS, and watch your codebase soar to new heights. Happy coding, and may the DI force be with you!
