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

How to dynamically update the link headers in the binding instance ? #164

Closed
cRicateau opened this issue Sep 24, 2018 · 6 comments
Closed

Comments

@cRicateau
Copy link

cRicateau commented Sep 24, 2018

I use graphql-binding in an express server to query another graphql server. On each request, I need to pass a header whose value changes everytime (it comes from the express req object and it is used for tracing ). I create a new HttpLink and a new GraphQLServiceBinding instance each time, but it creates a memory leak on my server (see code below).

Is there a way to dynamically change the headers in my link instance without re-creating a new instance of GraphQLServiceBinding each time ?

I use the function getGraphqlClient to create a graphql client in all my requests:

import { introspectSchema } from 'graphql-tools'
import fetch from 'node-fetch'
import { HttpLink } from 'apollo-link-http'
import { Binding } from 'graphql-binding'
import { makeRemoteExecutableSchema } from 'graphql-tools'

const graphqlServerUrl = process.env.GRAPHQL_URL

class GraphQLServiceBinding extends Binding {
  constructor(introspectedSchema, link) {
    const schema = makeRemoteExecutableSchema({
      link,
      schema: introspectedSchema,
    })

    super({ schema })
  }
}

let cachedSchema = null

async function getCachedSchema(link) {
  if (cachedSchema) return cachedSchema
  cachedSchema = await introspectSchema(link)
  return cachedSchema
}

// function getGraphqlClient is used to instantiate a new binding with specific headers
export const getGraphqlClient = async (graphQLServerUrl, headers = {}) => {
  const uri = graphQLServerUrl || process.env.GRAPHQL_URL
  if (!uri) {
    throw new Error('GRAPHQL_URL variable is mandatory')
  }

  try {
    // the headers change on every request
    const link = new HttpLink({ uri, fetch, headers })
    // the schema doesn't change
    const schema = await getCachedSchema(link)
    const binding = new GraphQLServiceBinding(schema, link)

    return binding
  } catch (err) {
    console.error(
      `Error introspecting remote schema on url ${graphQLServerUrl}`
    )
    throw err
  }
}
@kojuka
Copy link

kojuka commented Oct 15, 2018

@cRicateau were you able to figure this out? I have the same question

@nemcek
Copy link

nemcek commented Dec 2, 2018

I had similar use-case and I was able to figure it out.
Basically instead of passing fetch from node-fetch or cross-fetch to the constructor of HttpLink you can pass function which modifies the headers and then calls the mentioned fetch.

You can extend HttpLink class and do something like this:

import { HttpLink } from 'apollo-link-http';
import { fetch } from 'cross-fetch';
import { Request } from 'express';

class HttpLinkWithHeaders extends HttpLink {
  // Whole request or only headers if you want
  private req: Request = null;

  constructor() {
    super({
      uri: `....`,
      fetch: (input: RequestInfo, init?: RequestInit) => {
        // Modify init.headers based on this.req here

        return fetch(input, init);
      },
    });
  }

  // Call this on every request
  public setHtppRequest = (req: Request) => {
    this.req = req;
  };
}

@lambrojos
Copy link

lambrojos commented Dec 16, 2018

You can use the apollo-link-context module to set headers based on the request context:

const fetch = require('node-fetch');
const { Binding } = require('graphql-binding');
const { HttpLink } = require('apollo-link-http');
const { makeRemoteExecutableSchema, introspectSchema } = require('graphql-tools');
const { setContext } = require('apollo-link-context');

const http = new HttpLink({ uri: process.env.GRAPHQL_ENDPOINT, fetch });
const link = setContext((request, { graphqlContext }) => (
  graphqlContext ? {
    headers: {
      Authorization: `Bearer ${graphqlContext.auth}`,
    },
  } : {})).concat(http);

const schemaPromise = introspectSchema(link);

module.exports.graphql = schemaPromise
  .then(typeDefs => new class GraphQLBinding extends Binding {
    constructor() {
      super({
        schema: makeRemoteExecutableSchema({ link, schema: typeDefs }),
      });
    }
  }());

You can specify the context as a property of the third parameter in your bindings:

  const auth = 'auth token';
  const result = await bindings.query.whatevers({
    skip: 0,
    limit: 10,
  }, `{ items { id, body }}`, { context: { auth } });

@Urigo
Copy link
Collaborator

Urigo commented May 26, 2020

Thank you for reporting.

In the last few months, since the transition of many libraries under The Guild's leadership, We've reviewed and released many improvements and versions to graphql-cli, graphql-config and graphql-import.

We've reviewed graphql-binding, had many meetings with current users and engaged the community also through the roadmap issue.

What we've found is that the new GraphQL Mesh library is covering not only all the current capabilities of GraphQL Binding, but also the future ideas that were introduced in the original GraphQL Binding blog post and haven't come to life yet.

And the best thing - GraphQL Mesh gives you all those capabilities, even if your source is not a GraphQL service at all!
it can be GraphQL, OpenAPI/Swagger, gRPC, SQL or any other source!
And of course you can even merge all those sources into a single SDK.

Just like GraphQL Binding, you get a fully typed SDK (thanks to the protocols SDKs and the GraphQL Code Generator), but from any source, and that SDK can run anywhere, as a connector or as a full blown gateway.
And you can share your own "Mesh Modules" (which you would probably call "your own binding") and our community already created many of those!
Also, we decided to simply expose regular GraphQL, so you can choose how to consume it using all the awesome fluent client SDKs out there.

If you think that we've missed anything from GraphQL Binding that is not supported in a better way in GraphQL Mesh, please let us know!


In the context of that particular issue - GraphQL Mesh transform API to add and update the requests at any point in the process, so setting headers dynamically is easy.

We're looking forward for your feedback of how we can make your experience even better!

@Urigo Urigo closed this as completed May 26, 2020
@timotgl
Copy link

timotgl commented Aug 18, 2020

@cRicateau We also observed the memory leak when creating an instance of Binding on every request. The solution suggested by @lambrojos to dynamically set headers did not work for us (not 100% sure if we did it correctly though). Our solution was to use https://github.com/prisma-labs/graphql-request which makes it very easy to set headers on the client instance at any time. We haven't found any graphql client that provides the same auto-generated methods for queries and mutations though, like this project does. Haven't really investigated if https://the-guild.dev/blog/graphql-mesh does this, as it seems to covering different needs and we just needed a simple nodejs graphql client.

@Urigo
Copy link
Collaborator

Urigo commented Aug 18, 2020

@timotgl could you share a simple example in a repo?
then we could PR it with a couple of options so you could compare

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

No branches or pull requests

6 participants