moleculerjs / moleculer-apollo-server

:rocket: Apollo GraphQL server for Moleculer
MIT License
100 stars 56 forks source link

Can type-graphql be used with this? #11

Open Chrisigrimm opened 5 years ago

Chrisigrimm commented 5 years ago

Hey

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

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

duongdev commented 5 years ago

I don't think it can in the near feature

progresak commented 4 years ago

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?

AndreMaz commented 4 years ago

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

ujwal-setlur commented 4 years ago

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.

progresak commented 4 years ago

@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.

teezzan commented 3 years ago

@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?

ujwal-setlur commented 3 years ago

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

teezzan commented 3 years ago

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 commented 3 years ago

@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! jufopay@1.0.0 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 jufopay@1.0.0 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 commented 3 years ago

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 commented 3 years ago

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 commented 3 years ago

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

Thanks

ujwal-setlur commented 3 years ago

Yep

--ujwal On Nov 15, 2020, 4:16 PM -0800, Yusuf Taiwo Hassan notifications@github.com, 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.

iasbm commented 2 years ago

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 commented 2 years ago

@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.

ujwal-setlur commented 2 years ago

@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;
  }
}
iasbm commented 2 years ago

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