Development

NestJS: Resolving Dependency Injection - The Order Matters

3 min. read

At Tevpro we really like NestJS, it is a great framework for building server side typescript. It takes much of its inspiration from Angular. One of the most loved (and occasionally hated) features of Angular is its Dependency Injection (DI) framework. The NestJS team has done a nice job of bringing this DI framework to the server side. Being server side there are some notable differences, but all things considered, it's a refreshing change.

I ran into a familiar issue while building a new module, but was a bit baffled by the solution. First, frequently, I like to use index.ts files to simplify imports when multiple classes from the same folder need to be imported. Simple.

// index.ts

export * from './auth.controller';
export * from './auth.service';
export * from './constants';
export * from './jwt.strategy'
export * from './jwt-auth.guard';
export * from './local.strategy';
export * from './local-auth.guard';

Anyway, I had completed authoring my module and was about to start testing/debugging when I encountered a familiar error. The DI framework was unable to resolve on the dependencies (AuthService) in the AuthContoller.

// auth.contoller.ts

import { Controller, UseGuards, Req, Body } from '@nestjs/common';
import { AuthService, LocalAuthGuard, JwtAuthGuard } from '.';
import { Request } from 'express';
import { AuthGuard } from '@nestjs/passport';

@Controller('auth')
export class AuthController {

    constructor(
    private readonly authService: AuthService
    ) {}
    ...
}

Error

Nest can't resolve dependencies of the AuthController (?). Please make sure that the argument dependency at index [0] is available in the AuthModule context.

Potential solutions:
- If dependency is a provider, is it part of the current AuthModule?
- If dependency is exported from a separate @Module, is that module imported within AuthModule?
  @Module({
    imports: [ /* the Module containing dependency */ ]
  })

Normally this would be resolved by simply importing the module that contained AuthService and ensuring AuthService is a provider and exported in the module. However, in this particular case AuthService was part of the AuthModule and had already been added to the providers and exports.

// auth.module.ts

import { Module } from '@nestjs/common';
import { UserModule } from '../user/user.module';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { jwtConstants, LocalStrategy, JwtStrategy, AuthController, AuthService } from '.';
import { LoggerModule } from '~/shared/services/log';

@Module({
    imports: [
        LoggerModule.forRoot(),
        UserModule,
        PassportModule.register({ defaultStrategy: 'local' }),
        JwtModule.register({
            secret: jwtConstants.secret,
            signOptions: { expiresIn: '60s' },
        }),
    ],
    providers: [
        AuthService,
        LocalStrategy,
        JwtStrategy,
    ],
    exports: [
        AuthService,
        PassportModule,
    ],
    controllers: [AuthController],
})
export class AuthModule {}

The solution ended up being something I've encountered before but had long forgotten. Sometimes in DI, order seems to matter. Its not always in the place you might expect. Ultimately the solution was to re-order the index.ts file to move the controller to the bottom of the list.

// index.ts

export * from './constants';
export * from './jwt.strategy'
export * from './jwt-auth.guard';
export * from './local.strategy';
export * from './local-auth.guard';
export * from './auth.service';
export * from './auth.controller'; // this went from the top to the botttom

The theory here goes like this, the files that don't depend on other files within the same module go on top, next try to order as dependancies occur. This means it is probably easiest to build from the bottom up. Since the module will most likely import everything else it goes at the bottom. If there is a controller its probably next since it will likely import services, next would generally be services, things the services depend on and so on.

My understanding of this is that when you import from a index.ts file its not just an index (aka pointer) but it actually imports the contents of file being referenced like an include, therefore the code within the files is actually being evaluated in that order.

After making this change to the order, the application started right up.

If you like our content and want more tutorials, sign up for our monthly newsletter below. Or feel free to reach out to us on Twitter or via email with any questions.

Keith Kikta

NestJS • C# • AWS • Azure • Integration • EPM • CPM