Tsed: [Question] graphql integration

Created on 18 Dec 2018  路  8Comments  路  Source: tsedio/tsed

Informations

Type |聽Version
---|---
Question | 4.33.0


Description

any plan for integrating graphql or type-graphql stuff?
I want to use graphql with tsed but not work as i expected.
would you recommend me any example?

enhancement question

Most helpful comment

For type-graphql follow instructions from @Romakita above but replace GraphQLModule.createSchema method with this code:

    async createSchema() {
        const {resolvers} = this.settings;

        useContainer(this.injector);

        return await buildSchema({
            resolvers,
        });
    }

In my case complete GraphQLModule looks like this:

import { Constant, ExpressApplication } from "@tsed/common";
import { InjectorService, OnInit, Service } from "@tsed/di";
import { ApolloServer } from 'apollo-server-express';
import { buildSchema, useContainer } from 'type-graphql';

@Service()
export class GraphQLModule implements OnInit {
    @Constant("graphql", {})
    private settings: {[key: string]: any}; // options

    constructor(@ExpressApplication private expressApp: ExpressApplication, private injector: InjectorService) {}

    async $onInit() {
        const schema = await this.createSchema();

        const srv = new ApolloServer({ schema });
        srv.applyMiddleware({ app: this.expressApp, path: '/api/graphql' });
    }

    async createSchema() {
        const {resolvers} = this.settings;

        useContainer(this.injector);

        return buildSchema({
            resolvers,
        });
    }
}

works perfect

All 8 comments

Hello @bodguy
Great idea :)

It's possible to create a new package like tsed/mongoose or tsed/typeorm to support any third library. In the first time, you can try with your project locally by creating a new Service. Then if it works, we can create the new package inside the tsed project (very easy ;)).

For graphql, the idea is to configure the Apollo server with express (Doc: https://www.apollographql.com/docs/apollo-server/servers/express.html)

According to the example we can do that with a new Service as module. Create a new file GraphQLModule.ts:

import {Constant, OnInit, Service, ExpressApplication} from "@tsed/common";
import { ApolloServer, gql }  from 'apollo-server-express';

@Service()
export class GraphQLModule implements OnInit {
  @Constant("graphql", {})
  private settings: {[key: string]: any}; // options

  constructor(@ExpressApplication private expressApp: ExpressApplication) {}

  async $onInit(): Promise<any> | void {
    const {typeDefs, resolvers} = this.settings;
    const server = new ApolloServer({ typeDefs, resolvers });
    server.applyMiddleware({ app: this.expressApp });
  }
}

ServerLoader:

@ServerSettings({
   graphql: {
      typeDefs,   // give the typeDefs like is described in the apollo server example
      resolvers   // and same things for resolvers
   }
})

It's a basic approach but if it works, you can create a new package based on the typeorm package example here: https://github.com/Romakita/ts-express-decorators/tree/master/packages/typeorm

If you have any suggestion to improve the code or other things don't hesitate, you are welcome,
See you,
Romain

Thanks so much, I did try but I'm quite confusing. I don't really understand when the service is actually injected in to the graphql resolver. (using typeorm, type-graphql)

this is my graphql resolver class

@Service()
@Resolver(User)
export class UserResolver {
  constructor(private userService: UserService) {}

  @Query(returns => [User])
  async getAll(): Promise<User[]> {
    return await this.userService.find(); // this is UserResolver.ts:27
  }
}

UserService is just simple class accessing to database.

@Service()
export class UserService implements AfterRoutesInit {
  private connection: Connection;

  constructor(private typeORMService: TypeORMService) {

  }

  $afterRoutesInit(): Promise<any> | void {
    this.connection = this.typeORMService.get("development");
  }

  async find(): Promise<User[]> {
    const users = await this.connection.manager.find(User);

    return users;
  }
...

then graphql complain like below

"TypeError: Cannot read property 'find' of undefined",
            "    at UserResolver.<anonymous> (/Users/bodguy/Desktop/awesome-ts-server/src/resolvers/UserResolver.ts:27:35)",

I guess the problem caused by the order of service's lifecycle.
but I don't have any idea. can you help me?

Hum ok. I see what you want. The UserResolver class is instanciated two times. One, probably by the library of graphql and the second, by Ts.ED.

Here a kind of solution:

import {classOf, Metadata} from "@tsed/core";
import {Constant, ExpressApplication} from "@tsed/common";
import {Service, OnInit, InjectorService} from "@tsed/di"; 
import { ApolloServer, gql }  from 'apollo-server-express';

@Service()
export class GraphQLModule implements OnInit {
  @Constant("graphql", {})
  private settings: {[key: string]: any}; // options

  constructor(@ExpressApplication private expressApp: ExpressApplication, private injector: InjectorService) {}

  async $onInit(): Promise<any> | void {
    const schema = this.createSchema()
    const server = new ApolloServer(schema);
    server.applyMiddleware({ app: this.expressApp });
  }

  async createSchema() {
      const {typeDefs, resolvers} = this.settings;
      const schema = await buildSchema({
          resolvers,
          typeDefs
      });

     // we inject services on the current instance created by the typedGraphQL
     schema.resolvers.forEach((instance) => {
         (this.injector as any).bindInjectableProperties(instance); // I'll publish a new version to expose this method as public instead of private (if the code works ;) )
     });

     return schema;
  }
}

Then, the server can be configured like that:

@ServerSettings({
   graphql: {
      resolvers: [
          __dirname + "/**/*.resolver.ts"
      ]
   }
})

And finally create your resolver like that:

import {Inject} from "@tsed/common";  // or from "@tsed/di"
@Resolver(User)
export class UserResolver {
   @Inject()
   private userService: UserService

  @Query(returns => [User])
  async getAll(): Promise<User[]> {
     return await this.userService.find();
  }
}

I hope this code will work for you and we will produce a new package for graphQL ;)

See you,
Romain

For type-graphql follow instructions from @Romakita above but replace GraphQLModule.createSchema method with this code:

    async createSchema() {
        const {resolvers} = this.settings;

        useContainer(this.injector);

        return await buildSchema({
            resolvers,
        });
    }

In my case complete GraphQLModule looks like this:

import { Constant, ExpressApplication } from "@tsed/common";
import { InjectorService, OnInit, Service } from "@tsed/di";
import { ApolloServer } from 'apollo-server-express';
import { buildSchema, useContainer } from 'type-graphql';

@Service()
export class GraphQLModule implements OnInit {
    @Constant("graphql", {})
    private settings: {[key: string]: any}; // options

    constructor(@ExpressApplication private expressApp: ExpressApplication, private injector: InjectorService) {}

    async $onInit() {
        const schema = await this.createSchema();

        const srv = new ApolloServer({ schema });
        srv.applyMiddleware({ app: this.expressApp, path: '/api/graphql' });
    }

    async createSchema() {
        const {resolvers} = this.settings;

        useContainer(this.injector);

        return buildSchema({
            resolvers,
        });
    }
}

works perfect

useContainer ? It works with the injector ? XD

Yep, It works with everything that has .get(target: T): T method and uses it to inject dependencies itself :)

nice :)
Somebody want to write the package and/or new documentation page ?

I can try a bit later when i'm sure there's no pitfalls.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

teusemanuel picture teusemanuel  路  4Comments

haroon407 picture haroon407  路  4Comments

dorian-kwon picture dorian-kwon  路  3Comments

yangukmo picture yangukmo  路  6Comments

yangukmo picture yangukmo  路  5Comments