moleculer-apollo-server icon indicating copy to clipboard operation
moleculer-apollo-server copied to clipboard

Can type-graphql be used with this?

Open Chrisigrimm opened this issue 6 years ago • 17 comments

Hey

is there a way i can use this with type-graphql?

https://github.com/19majkel94/type-graphql

Chrisigrimm avatar Feb 23 '19 10:02 Chrisigrimm

I don't think it can in the near feature

duongdev avatar Mar 18 '19 03:03 duongdev

Hey @duongdev is there any progress on this Issue? Is it somehow possible to use type-graphql with moleculer-apollo-server ? :) what are the alternatives?

progresak avatar Apr 25 '20 20:04 progresak

@ujwal-setlur mentioned at Discord that he's using TypeGraphQL. Maybe you can ask him

AndreMaz avatar Apr 25 '20 21:04 AndreMaz

Yes, I am using type-graphql with apollo and moleculer. However, I am NOT using the moleculer-apollo-server package, the reason being I wanted only the API gateway to be GraphQL aware, and all other services to be pure moleculer services without any knowledge of GraphQL.

So, what I have is a moleculer service acting as an API gateway using standard apollo and type-graphql. I use type-graphql to build the GraphQL schema, queries, and resolvers. The API service essentially starts in the service's start handler the apollo server with the schema built by type-graphql. It also injects the moleculer broker into the apollo context so that queries and resolvers can access the broker via the context, and can make standard moleculer service action and event calls to other services.

graphql/index.ts

// TypeGraphQL
import 'reflect-metadata';
import { buildSchemaSync } from 'type-graphql';

// Import our GraphQL API piece by piece
// Each module here returns a TypeGraphQL resolver

// Our stuff
import { UserResolver } from './user/index';

type NonEmptyArray<TItem> = [TItem, ...TItem[]];

// Our array of resolvers
const resolvers: NonEmptyArray<Function> = [UserResolver];

// Build our schema
const schema = buildSchemaSync({ validate: false, resolvers });

// Export our schema!
export default schema;

api.service.ts

import moleculer from 'moleculer';
import { Action, Service } from 'moleculer-decorators';
import { ApolloServer } from 'apollo-server';

// Our stuff
import schema from './graphql/index'; // <-- schema built by TypeGraphQL

// Define our api service
@Service({
  name: 'api'
})
export class ApiService extends moleculer.Service {
  server!: ApolloServer;

  // Startup handler
  async started() {
    const moleculerBroker = this.broker;

    // Create our Apollo GraphQL server here
    const server = new ApolloServer({
      schema,
      context: () => ({
        moleculerBroker
      })
    });
    this.server = server;

    // Check if port is set, otherwise default to 3000
    const port = process.env.PORT || 3000;

    // Start our server
    await server.listen({ port }).then(({ url }) => {
      this.broker.logger.info(`🚀  Apollo Server ready at ${url}`);
    });
  }

  // Stopped handler
  stopped() {
    this.server.stop();
  }
}

Hope this was helpful. Happy to answer any questions.

ujwal-setlur avatar Apr 26 '20 04:04 ujwal-setlur

@ujwal-setlur thank you so much. It actually solves my problem as well. Thanks to you I realized I need to use neither moleculer-apollo-server or moleculer-web at all since I need to have just API GraphQL backend.

progresak avatar Apr 26 '20 09:04 progresak

@ujwal-setlur I did exactly as you stated above and I ended up with Service name can't be empty! Maybe it is not a valid Service schema. Maybe is it not a service schema?. Am I doing this wrong?

teezzan avatar Nov 13 '20 20:11 teezzan

@teezzan I'll need to see some code snippets...

ujwal-setlur avatar Nov 13 '20 21:11 ujwal-setlur

Thanks for your response. My laptop is dead right now. I will share the snippets once it's up. However, the code is as you wrote above. I was simply setting it up when it gave the error in the api.service.ts file.

teezzan avatar Nov 13 '20 22:11 teezzan

@ujwal-setlur Here is the code I am using currently. My aim to to get the server up and running before starting to implement the schema. The versions of some libraries are as follows.

    "reflect-metadata": "^0.1.13",
    "type-graphql": "^1.1.1",
    "typescript": "^3.8.3",
    "graphql": "^15.4.0",
    "moleculer": "^0.14.0",

LH/graphql/recipe/index.ts contains the following.

import 'reflect-metadata';
import { Field, ObjectType, Resolver, Query, Arg } from 'type-graphql';


@ObjectType({ description: "Object representing test" })
class Recipe {
    @Field()
    title: string;
}

@Resolver(Recipe)
export class RecipeResolver {
    constructor() { }

    @Query(returns => String)
    async recipe() {
        return "Hello recipe";
    }

}

LH/graphql/index.ts contains

// TypeGraphQL
import 'reflect-metadata';
import { buildSchemaSync } from 'type-graphql';

// Import our GraphQL API piece by piece
// Each module here returns a TypeGraphQL resolver

// Our stuff
import { RecipeResolver } from './recipe/index';

type NonEmptyArray<TItem> = [TItem, ...TItem[]];

// Our array of resolvers
const resolvers: NonEmptyArray<Function> = [RecipeResolver];

// Build our schema
const schema = buildSchemaSync({ validate: false, resolvers });

// Export our schema!
export default schema;

LH/services/api.service.ts contains

import moleculer from 'moleculer';
import { Action, Service } from 'moleculer-decorators';
import { ApolloServer } from 'apollo-server';

// Our stuff
import schema from '../graphql/index'; // <-- schema built by TypeGraphQL

// Define our api service
@Service({
    name: 'api'
})
export class ApiService extends moleculer.Service {
    server!: ApolloServer;

    // Startup handler
    async started() {
        const moleculerBroker = this.broker;

        // Create our Apollo GraphQL server here
        const server = new ApolloServer({
            schema,
            context: () => ({
                moleculerBroker
            })
        });
        this.server = server;

        // Check if port is set, otherwise default to 3000
        const port = process.env.PORT || 3000;

        // Start our server
        await server.listen({ port }).then(({ url }) => {
            this.broker.logger.info(`🚀  Apollo Server ready at ${url}`);
        });
    }

    // Stopped handler
    stopped() {
        this.server.stop();
    }
}

The complete error log is as follows.

[2020-11-14T16:28:11.965Z] INFO  gal1l30-inspiron-3521-6867/BROKER: Registered 14 internal middleware(s).
Service name can't be empty! Maybe it is not a valid Service schema. Maybe is it not a service schema? { schema: { ApiService: [Function] } }
[2020-11-14T16:28:19.792Z] ERROR gal1l30-inspiron-3521-6867/BROKER: Failed to load service '/home/gal3li0/Documents/Apis/bstech/LH/services/api.service.ts' ServiceSchemaError: Service name can't be empty! Maybe it is not a valid Service schema. Maybe is it not a service schema?
    at Service.parseServiceSchema (/home/gal3li0/Documents/Apis/bstech/LH/node_modules/moleculer/src/service.js:92:10)
    at new Service (/home/gal3li0/Documents/Apis/bstech/LH/node_modules/moleculer/src/service.js:63:9)
    at ServiceBroker.createService (/home/gal3li0/Documents/Apis/bstech/LH/node_modules/moleculer/src/service-broker.js:805:14)
    at ServiceBroker.loadService (/home/gal3li0/Documents/Apis/bstech/LH/node_modules/moleculer/src/service-broker.js:770:16)
    at /home/gal3li0/Documents/Apis/bstech/LH/node_modules/moleculer/src/runner.js:382:50
    at Array.forEach (<anonymous>)
    at MoleculerRunner.loadServices (/home/gal3li0/Documents/Apis/bstech/LH/node_modules/moleculer/src/runner.js:382:25)
    at MoleculerRunner.startBroker (/home/gal3li0/Documents/Apis/bstech/LH/node_modules/moleculer/src/runner.js:453:8)
    at /home/gal3li0/Documents/Apis/bstech/LH/node_modules/moleculer/src/runner.js:475:21 {
  code: 500,
  type: 'SERVICE_SCHEMA_ERROR',
  data: { schema: { ApiService: [Function] } },
  retryable: false
}
[Runner] Service name can't be empty! Maybe it is not a valid Service schema. Maybe is it not a service schema? ServiceSchemaError: Service name can't be empty! Maybe it is not a valid Service schema. Maybe is it not a service schema?
    at Service.parseServiceSchema (/home/gal3li0/Documents/Apis/bstech/LH/node_modules/moleculer/src/service.js:92:10)
    at new Service (/home/gal3li0/Documents/Apis/bstech/LH/node_modules/moleculer/src/service.js:63:9)
    at ServiceBroker.createService (/home/gal3li0/Documents/Apis/bstech/LH/node_modules/moleculer/src/service-broker.js:805:14)
    at ServiceBroker.loadService (/home/gal3li0/Documents/Apis/bstech/LH/node_modules/moleculer/src/service-broker.js:770:16)
    at /home/gal3li0/Documents/Apis/bstech/LH/node_modules/moleculer/src/runner.js:382:50
    at Array.forEach (<anonymous>)
    at MoleculerRunner.loadServices (/home/gal3li0/Documents/Apis/bstech/LH/node_modules/moleculer/src/runner.js:382:25)
    at MoleculerRunner.startBroker (/home/gal3li0/Documents/Apis/bstech/LH/node_modules/moleculer/src/runner.js:453:8)
    at /home/gal3li0/Documents/Apis/bstech/LH/node_modules/moleculer/src/runner.js:475:21 {
  code: 500,
  type: 'SERVICE_SCHEMA_ERROR',
  data: { schema: { ApiService: [Function] } },
  retryable: false
}
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] dev: `ts-node ./node_modules/moleculer/bin/moleculer-runner.js --hot --repl --env --config moleculer.config.ts services/**/*.service.ts`
npm ERR! Exit status 1
npm ERR! src
npm ERR! Failed at the [email protected] dev script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /home/gal3li0/.npm/_logs/2020-11-14T16_28_19_888Z-debug.log

Thanks

teezzan avatar Nov 14 '20 16:11 teezzan

Thanks @ujwal-setlur and @progresak . I solved it by looking through @progresak project. I needed to add a this.name = 'api' to the started async function. I didn't think of that.

Thanks.

teezzan avatar Nov 15 '20 09:11 teezzan

Does anyone have any idea on how to get typegraphql authorization working? I figured it would require extracting the authorization headers from req. Unfortunately, I haven't been able to lay my hands on req at all. I tried using global Middleware but it isn't getting called at all. Any help?

teezzan avatar Nov 15 '20 23:11 teezzan

Solved... Apollo Server context to the rescue. https://www.apollographql.com/docs/apollo-server/security/authentication/

Thanks

teezzan avatar Nov 16 '20 00:11 teezzan

Yep

--ujwal On Nov 15, 2020, 4:16 PM -0800, Yusuf Taiwo Hassan [email protected], wrote:

Solved... Apollo Server context to the rescue. https://www.apollographql.com/docs/apollo-server/security/authentication/ Thanks — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or unsubscribe.

ujwal-setlur avatar Nov 17 '20 01:11 ujwal-setlur

Thanks for this thread and it kind of gives options for us to similar issue we are aiming to resolve. The only thing we like to know is how can we handle /route a GraphQL Query ( from client) inside the API G/W ( GraphQL aware just as per the implementation samples posted by @ujwal-setlur and @teezzan ) to invoke pure Moleculer Micro Services (Not GraphQL Aware) to fulfil the GQL request by using the Resolvers please. i.e can you please clarify which of the below flow you implemented? Flow-1 --> Client with GQL Reqeust --> API GW with Apollo server --> Moleculer Service (non GraphQL Aware)--> Resolver --> Repository --> DB

Flow-2 --> GQL Reqeust --> API GW with Apollo server --> Resolver ( here resolver retrieves the broker from ctx passed down from Apolloserver in Api GW Service) -->ctx.broker.call (Moleculer Service (non GraphQL Aware))-->Repository --> DB?

Truly appreciate if we can see some samples for this please

iasbm avatar Jul 22 '22 08:07 iasbm

@ujwal-setlur and @teezzan we managed to get it working by using "Flow-2" approach as per my message above and I presume you also must have used Flow-2. If by any chance you got it working through Flow-1 then appreciate if you can share some code snippet pls.

iasbm avatar Jul 22 '22 15:07 iasbm

@iasbm I am not quite clear on Flow-1, but my implementation is similar to Flow-2:

@Resolver()
export class PingResolver {
  @Query(() => String)
  async Ping(@Ctx() ctx: ResolverContext): Promise<String> {
    const { moleculerBroker } = ctx;
    const response = moleculerBroker.call('authz.ping');
    return response;
  }
}

ujwal-setlur avatar Jul 22 '22 18:07 ujwal-setlur

thanks @ujwal-setlur for confirming this. Your confirmation aligns with our implementation of Flow-2, cheers

iasbm avatar Jul 22 '22 18:07 iasbm