Daniel Sottile
Daniel Sottile

Reputation: 91

I am getting an "There is no matching message handler defined in the remote service." for my NestJS Microservice

I am creating a NestJS 'Auth' microservice that our other graphql microservices can use, mostly to create guards for each microservice. In this case let's just call it 'Other'. I have followed multiple documentation sources, checked that the port is open and listening, made sure the message handler matches, yet I continue to get a "There is no matching message handler defined in the remote service." error when making a graphql call that uses the guard.

I have tried both setting up proxy connections via both ends, tried moving the auth tcp microservice to its own nestjs project, tried different messageRequest patterns all with the same result. I highly suspect the Auth Microservice is correct and the errors are in the other microservice, as this is my first time trying to do this.

Auth Microservice:

main.ts:

import { NestFactory } from '@nestjs/core';
import { Transport } from '@nestjs/microservices';
import { ConfigService } from '@nestjs/config';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const configService = app.get(ConfigService);
  const port = Number(configService.get('APP_PORT'));
  app.connectMicroservice(
    {
      name: 'AUTH_CLIENT',
      transport: Transport.TCP,
      options: {
        host: 'localhost',
        port: 4000,
      },
    },
    { inheritAppConfig: true },
  );

  await app.startAllMicroservices();

  await app.listen(port || 3000);
}
bootstrap();

app.module.ts:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { ConfigModule } from '@nestjs/config';
import configuration from './config/configuration';
import { AppService } from './app.service';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import GraphQLJSON from 'graphql-type-json';
import { OktaUserModule } from './okta-user/okta-user.module';
import { AuthModule } from './auth/auth.module';

@Module({
  imports: [
    GraphQLModule.forRoot<ApolloDriverConfig>({
      resolvers: { JSON: GraphQLJSON },
      driver: ApolloDriver,
      debug: false,
      playground: true,
      autoSchemaFile: true,
    }),
    ConfigModule.forRoot({ load: [configuration] }),
    OktaUserModule,
    AuthModule,
    ConfigModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

auth.controller.ts:

import { Controller } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
import { AuthService } from './auth.service';

@Controller()
export class AuthController {
  constructor(private readonly authService: AuthService) {}

  @MessagePattern('checkJWT')
  async isAuthenticated(data: { jwt: string }) {
    // this seems to never be triggered
    try {
      const res = await this.authService.validateToken(data.jwt);

      return res;
    } catch (error) {
      console.log(error);
      return false;
    }
  }
}

Other Microservice:

main.ts:

import { NestFactory } from '@nestjs/core';
import { AppModule } from '@/app.module';
import configuration from '@/config/';


async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.enableCors();
  await app.listen(configuration().port);
}

bootstrap();

app.module.ts:

import { ApolloFederationDriver, ApolloFederationDriverConfig } from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { APP_GUARD } from '@nestjs/core';
import { GraphQLModule } from '@nestjs/graphql';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { MongooseModule } from '@nestjs/mongoose';

// import { ApolloServerPluginLandingPageLocalDefault } from 'apollo-server-core';
import GraphQLJSON from 'graphql-type-json';
import { UUID } from '@/common';
import configuration from '@/config';
import { AuthGuard } from './common/guards/auth.guard';


@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
    }),
    MongooseModule.forRoot(configuration().database.connectionString),
    GraphQLModule.forRoot<ApolloFederationDriverConfig>({
      driver: ApolloFederationDriver,
      autoSchemaFile: true,
      playground: true,
      // I've turned the playground on for testing locally as it is faster
      // plugins: [ApolloServerPluginLandingPageLocalDefault()],
      buildSchemaOptions: {
        dateScalarMode: 'isoDate',
      },
      resolvers: { UUID, JSON: GraphQLJSON },
    }),
    ClientsModule.register([
      {
        name: 'AUTH_CLIENT',
        transport: Transport.TCP,
        options: {
          host: 'localhost',
          port: 4000,
        },
      },
    ]),
    // other module services we dont really care about,
  ],
  providers: [
    {
      provide: APP_GUARD,
      useClass: AuthGuard,
    },
  ],
})
export class AppModule {}

auth.guard.ts:

import { Inject, CanActivate, ExecutionContext } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { ClientProxy } from '@nestjs/microservices';
import { firstValueFrom, timeout } from 'rxjs';

export class AuthGuard implements CanActivate {
  constructor(
    @Inject('AUTH_CLIENT')
    private readonly client: ClientProxy,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const { req } = GqlExecutionContext.create(context).getContext();

    try {
      const res = await firstValueFrom(
        this.client
          .send('checkJWT', { jwt: req.headers.authorization?.split(' ')[1] })
          .pipe(timeout(5000)),
      );

      return res;
    } catch (err) {
      console.log(err);
      return false;
    }
  }
}

The auth guard is at least getting the graphql query, as it always returns 403 forbidden (I can make this 401 later as per an unauthorizedException rather than false), but for some reason the Auth microservice never receives the message. I have double checked that this port is open and running as well.

Upvotes: 1

Views: 1588

Answers (1)

Daniel Sottile
Daniel Sottile

Reputation: 91

I suppose there's no such thing as a dumb question, but here's the dumb answer:

I forgot to include the controller in the auth module:

@Module({
  imports: [ConfigModule],
  controllers: [AuthController], // yeah.....
  providers: [AuthService],
})
export class AuthModule {}

Upvotes: 1

Related Questions