Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic resolvers based on express request #2560

Closed
keesvanlierop opened this issue Apr 11, 2019 · 4 comments
Closed

Dynamic resolvers based on express request #2560

keesvanlierop opened this issue Apr 11, 2019 · 4 comments

Comments

@keesvanlierop
Copy link

keesvanlierop commented Apr 11, 2019

In version 1 of apollo-server-express, the way to pass the schema and resolver set was to return the information in the graphqlExpress util, like:

graphqlExpress((req?: Request) => {
    // Create schema based on request
    ...

    return {
        context,
        formatError: (error: GraphQLError) => ...,
        schema,
    }
})

With this setup, it used to be easy to create dynamic resolver sets, based on the context of the express request.

In version 2 of apollo-server-express, schema's are provided when creating a new ApolloServer, before the server is actually running. Because of this change, we loose the ability to have this dynamic resolver setup.

How to accomplish this in version 2 (apart from shifting the logic to the resolvers itself) or why was support dropped for this?

@sbrichardson
Copy link
Contributor

To get the v1 functionality, you need to create a custom class YourServer extends ApolloServer and override one method applyMiddleware. You can also choose to modify the root context if needed.

You are basically skipping the server's this.schema that is saved on startup, like v1.

You have to do similar for subscriptions, since those requests take a different path.

Note the graphqlExpress import at top.

import { renderPlaygroundPage } from '@apollographql/graphql-playground-html'
import { ApolloServer, makeExecutableSchema } from 'apollo-server-express'
import { graphqlExpress } from 'apollo-server-express/dist/expressApollo'


class DynamicServer extends ApolloServer {
  applyMiddleware({ app, path }) {
    app.use(path, async (req, res, next) => {
      if (ENABLE_PLAYGROUND) {
        // copy playground code from apollo applyMiddleware source
      }

      const customSchema = getYourCustomOrCachedSchemaBasedOnRequest()

     // this.context is the current server / request context, you can keep or override below
      const contextObj = { ...this.context, request: req } // ...other context etc

      const gqlOptions = {
        ...this,
        graphqlPath: path,
        schema: await makeExecutableSchema(customSchema),
        context: contextObj,
      }
      return graphqlExpress(super.createGraphQLServerOptions.bind(gqlOptions))(
        req,
        res,
        next
      )
    })
  }

@abernix
Copy link
Member

abernix commented Jul 9, 2019

I'm going to close this in lieu of the above suggestion. It's also worth noting that, using the Express integration (i.e. apollo-server-express), it's now possible to construct multiple Apollo Server instances and use the getMiddleware method on each to get the appropriate middleware and bind it depending on route specific context (or other properties).

@abernix abernix closed this as completed Jul 9, 2019
@wbeard
Copy link

wbeard commented Aug 22, 2019

@abernix Based on your last statement, are you saying that JIT schema generation and resolver mapping is supported by apollo-server-express out of the box?

@carolinmaisenbacher
Copy link

Hello everybody.
I am currently trying to implement a dynamic schema, based on the token in the request.

I tried to follow the described solution above, but I realised that the getMiddleware function is applying several middlewares and I didn't want to just stupidly copy and paste that code.
So I now tried to just override the the one middleware that actually inits the GraphqlExpress Server.

It runs now, but the newly generated schema isn't picked up, but instead the default schema that I pass in when I init the server is picked up:
new DynamicServer({schema: defaultSchema})

So it seems like my newly generated schema can't override the initial one (which I have to pass in, otherwise it throws an error)

Does maybe someone know what I am missing here?

class DynamicServer extends ApolloServer {
  applyMiddleware({ app, path, ...rest }) {
    const router = this.getMiddleware({ path, rest });

    // delete middleware that initializes the default apollo server
    router.stack.pop();
  
    // init dynamic schema server
    router.use(path, async (req, res, next) => {
      if (this.playgroundOptions && req.method === "GET") {
        // here I copy and pasted the code from the ApolloServer source code
      }
      const token = req.headers.authorization;
     let userId = null;
      try {
        userId = await verifyToken(token);
      } catch (error) {
        throw new AuthenticationError("Authentication Token required.");
      }
  

      /* Not necessary, but removing to ensure schema built on the request */
      const { schema, ...serverObj } = this;

      const contextObj = {
        ...serverObj.context,
        request: req,
        me: userId,
      };
      const newSchema = await generateSchema(userId);

      const graphqlOptions = {
        ...serverObj,
        graphqlPath: path,
        schema: newSchema,
        context: contextObj
      };

      return graphqlExpress(
        super.createGraphQLServerOptions.bind(graphqlOptions)
      )(req, res, next);
    });

    // Finally applying all middleware
    app.use(router);
  }
}

const dynamicAdminServer = new DynamicServer({
  /* adding a default graphql schema initially */
  typeDefs: `type Query { _: Boolean }`
});
dynamicAdminServer.applyMiddleware({ app, path: "/test" });

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants