NestJS - The missing piece to easily develop full-stack TypeScript web applications

Michael Hoffmann on April 15, 2019

I think we all know this problem: You need to develop a new web application and therefore you need to implement the core of the application in the ... [Read Full]
markdown guide
 

I imagine this is very exciting for people that like dependency injection, but I have stopped seeing the value in it (especially in TS/JS). I used it for many years in C# and I understand it’s place there. But since Jest and Sinon allow you to do runtime substitution of any object in a test, then is inversion if control really necessary?

I’d very much be interested in your answer becaus I’m nervous that I’m missing something.

 

Well I can't speak to DI in general, but in angular I will occasionally want to replace injected services with a new class when consuming them in a different environment (e.g. on the server vs the browser). Alternatively, I've had shared services that are extended or modified by individual applications or individual (Angular) components. Angular's DI system makes this easy.

As an example: I have a constellation of apps that rely on the same UserAccountService. Most of the apps don't require a user to be logged in, but one app does require users to be logged in. Among other things, the app that requires users to be logged in extends the standard UserAccountService and overwrites it's getCurrentUser() method so that if the current user is null it redirects to the login page. Because of DI, any components calling UserAccountService#getCurrentUser(), including shared library components, will make use of the replaced service.

It took me a while to get comfortable with Angular's DI system, and I certainly did fine before I really understood how to use it so I don't view it as an absolutely necessary feature. Now that I'm comfortable with Angular's DI system however, I find it incredibly useful. I imagine in some cases you could accomplish something similar using file replacements in your build system (e.g. file replacements in webpack). It would be very hard to distribute a public module this way, however, as it would require consumers to fiddle with their build system.

 

You can avoid the big complicated DI of Angular by just using function DI with native JS.

So instead of:

function myController(){
    const fruitCount = global.dependency1.countOfFruit()
    const veggieCount = global.dependency2.countOfVegies()
}

You can invert control:

function myController(dependency1, dependency2){
    const fruitCount = dependency1.countOfFruit()
    const veggieCount = dependency2.countOfVegies()
}

Now you can mock those two dependencies by passing in a substitute. No Angular required.

Well

  1. No one is claiming that Angular is required. Any React dev can tell you that Angular is not required LOL.

  2. Deep within a shared module which I don't control, I do not have the ability to pass arguments to function myController(dependency1, dependency2) The maintainer of that shared module would need to publicly expose the ability to pass in new dependencies, and at that point you're basically building a DI system. Might as well use one that already exists.

  3. Angular's dependency injection (and, based on the examples in this post, nestjs' as well) already looks like your "invert control" example:

export class PersonClass {
  constructor(
    public dependency1: Service1,
    public dependency2: Service2
  ) {}
}

Without dependency injection though, you need to manually supply dependency1 and dependency2 all over the place, as well as manually instantiate classes. Super annoying. Angular analyzes the types and "automatically" / transparently supplies the appropriate input arguments.

I'm really not looking for an argument though. I was just trying to answer your question "what am I missing?". Clearly you're happy with the way you're currently doing things. There's no reason to pick up a tool you don't need.

Yup, I’m not arguing, And I do appreciate you trying to help me find what I might be missing. But I’m just hoping to share that Jest, Sinon, and most JS libraries that impliment spies allow you to mock any dependency. So the following might not be necessarily true.

Deep within a shared module which I don't control, I do not have the ability to pass arguments to function myController(dependency1, dependency2) The maintainer of that shared module would need to publicly expose the ability to pass in new dependencies

Well, I mean it is 100% true, but since Jest can mock import statements, you don’t necessarily have to invert control to pass a spy in because the module loader does that for IOC for you.

Thank you for your thoughts. If someone would be willing to verify my hypothesis, I would appreciate it. So far I have not needed DI, but there might be circumstances I haven’t encountered.

Again, I enjoyed your article and I appreciate your responses. :)

But I’m just hoping to share that Jest, Sinon, and most JS libraries that impliment spies allow you to mock any dependency.

In my case, I haven't used DI for tests but rather for production code. As a real world example, this angular module for working with cookies allows you to replace the CookieService with a CookieBackendService when rendering your app on the server side. As always with programming, I'm sure there are other ways you could accomplish the same thing.

 

As long as you are productive and your team can handle the code, you do not need to use DI. I just prefer it as I am used to it from using Angular.

 

Very good article! Do you know anything about @nestjs/swagger support for OpenAPI 3?

PS. The jhipster team/comunnity is already working on NHipster (a JHipster blueprint to generate NestJS applications), take a look here: generator-jhipster-nodejs

 

Thanks! Unfortunately, I am not informed about OpenAPI 3 support of @nestsjs/swagger but you can check this GitHub thread: github.com/nestjs/swagger/issues/1...

 

Very well written and good overview of Nest! Thank you!

code of conduct - report abuse