typescriptnestjs

NestJS magical imports - Nest can't resolve dependencies


So I was following this tutorial https://www.tomray.dev/nestjs-nextjs-trpc

and after finishing NestJS part discovered that my code not working and throwing this error while my code looked identical to the one from the tutorial.

[Nest] 94625  - 10/01/2024, 11:43:53 AM   ERROR [ExceptionHandler] Nest can't resolve dependencies of the TrpcRouter (?). Please make sure that the argument Function at index [0] is available in the TrpcModule context.

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

Here's the code of that module:

import { Injectable, type INestApplication } from '@nestjs/common';
import type { TrpcService } from './trpc.service';
import { z } from 'zod';
import * as trpcExpress from '@trpc/server/adapters/express';

@Injectable()
export class TrpcRouter {
  constructor(private readonly trpc: TrpcService) {}

  appRouter = this.trpc.router({
    hello: this.trpc.procedure
      .input(z.object({ name: z.string().optional() }))
      .query(({ input }) => {
        return `Hello ${input.name ? input.name : 'World'}!`;
      }),
  });

  async applyMiddleware(app: INestApplication) {
    app.use(
      '/trpc',
      trpcExpress.createExpressMiddleware({ router: this.appRouter }),
    );
  }
}

export type AppRouter = TrpcRouter['appRouter'];

After trying different things I discovered that thing that lead to this error is the fact that I'm importing type, not a whole module at this line

import type { TrpcService } from './trpc.service';

and after changing it to original tutorial version

import { TrpcService } from './trpc.service';

it started working properly.

But I'm using it only as type, not as value

  constructor(private readonly trpc: TrpcService) {}

So the question is - why this happens? Why it throws error when I'm only importing type and not the value? I suppose that's how NestJS works, or am I missing something in how typescript handles it?

PS: there are other questions similar to this, but none of them talking about this particular differences - type import/regular import

PPS: searching for type imports I found this issue which may be the answer to my question. Reading it now https://github.com/nestjs/nest/issues/5421


Solution

  • If you still want to use import type for some reason. You can use other way define provider for container Nest's DI system. When define provider

    providers: [
      {
        provider: "TrpcSvc", // use string or symbol at here
        useClass: TrpcService
      }
    ]
    ...
    import type { TrpcService } from './trpc.service';
    
    // Inject
    constructor(@Inject("TrpcSvc") private readonly trpc: TrpcService) {}
    

    The most important is way that you import provider in module. If you use default import provider: [TrpcService], it mean you add class TrpcService to container of Nest's DI system with key and value is class TrpcService. It like this syntax

    import { TrpcService } ....
    ...
    providers: [
      {
        provider: TrpcService,
        useClass: TrpcService
    
      }
    ]
    

    In opposite, NestJS don't support define type for key of providers. It is code NestJS define module options.

    ...
    /**
     * Interface defining the property object that describes the module.
     *
     * @see [Modules](https://docs.nestjs.com/modules)
     *
     * @publicApi
     */
    export interface ModuleMetadata {
        ...
        /**
         * Optional list of providers that will be instantiated by the Nest injector
         * and that may be shared at least across this module.
         */
        providers?: Provider[];
        ...
    }
    ...
    // For class
    export interface Type<T = any> extends Function {
        new (...args: any[]): T;
    }
    ...
    export type Provider<T = any> = Type<any> | ClassProvider<T> | ValueProvider<T> | FactoryProvider<T> | ExistingProvider<T>;
    
    

    Currently, provider only accept value not type.