Graphql merge (combine) multiple queries into one?

Graphql merge (combine) multiple queries into one?


22

I’m trying to combine multiple GraphQL queries into one query using JavaScript.
I am looking for something like this:

let query3 = mergeQueries(query1, query2);

We won’t know beforehand which queries will be combined.

Suppose I have queries like this:

input query1:

{
  post(id: 1234) {
    title
    description
  }
}

input query2:

{
  post(id: 1234) {
    tags
    author {
      name
    }
  }
}

Then I would like the result query3 to be:
result query3:

{
  post(id: 1234) {
    title
    tags
    description
    author {
      name
    }
  }
}

This would be the same functionality as lodash _.merge() does for JSON objects, but then with GraphQL queries instead of JSON objects.

2

  • are you trying to minimize the number of HTTP requests? if yes this might help blog.apollographql.com/query-batching-in-apollo-63acfd859862

    – Navid Yousefzai

    Feb 18, 2019 at 9:35


  • 1

    You can't do this through simple textual manipulation; you need to parse the GraphQL queries and join them together. That can get complicated in the presence of dynamic type matching and fragments.

    – David Maze

    Feb 18, 2019 at 11:56

5 Answers
5


13

My answer is getting a bit to long for a comment so I decided to write a direct answer here. First I think both comments are really helpful. There is a solution that let’s two queries share an HTTP request which might already be the optimisation you are looking for. Furthermore merging queries is not trivial. It requires a lot of effort to do this down to a field level. Not only fragments can make this difficult, you also have to take variables into account. As far as I know there is no solution publicly available to do that so you would have to do it yourself. Also I have not heard of a company that does this. I think that the fact that there is no solution is an indicator that it might not be worth it to do it.

I can only guess your problem, but another way to reduce the amount of queries sent by a frontend application is to make use of fragments. While your fragments cannot have variables a healthy component structure will still fit very well with fragments:

fragment PostHeader on Post {
  title
  description
}

fragment PostMeta on Post {
  tags
  author {
    name
  }
}

query {
  post(id: 1234) {
    ...PostHeader
    ...PostMeta
  }
}

2

  • This looks like a very useful solution in many cases. I expect a lot of overlap between the input queries which will make this solution less ideal, so I will look into building my own merge function.

    – Hendrik Jan

    Feb 19, 2019 at 7:00

  • 1

    We wrote some code for combining fragments, with automatic naming of the fragments and removing duplicate fragments, and released it here: github.com/SVT/graphql-defragmentizer

    – Anders Kindberg

    Aug 23, 2019 at 7:19


7

Thanks to parameterized fragments you can take variables into account! Assuming post is a field of the root query type the combined query referring to the above example would be:

fragment PostHeader on RootQueryType {
  post(id: $id) {
    tags
    author {
      name
    }
  }
}

fragment PostMeta on RootQueryType {
  post(id: $id) {
    tags
    author {
      name
    }
  }
}

# ID being the id type
query($id: ID! = 1234) {
  ...PostHeader
  ...PostMeta
}

or rather in a real-world scenario you’d be passing in the id dynamically (e.g. in your post request), see: https://graphql.org/learn/queries/#variables


6

I wrote a lib for this: https://github.com/domasx2/graphql-combine-query

import comineQuery from 'graphql-combine-query'

import gql from 'graphql-tag'

const fooQuery = gql`
  query FooQuery($foo: String!) {
    getFoo(foo: $foo)
  }
`

const barQuery = gql`
  query BarQuery($bar: String!) {
    getBar(bar: $bar)
  }
`

const { document, variables } = combineQuery('FooBarQuery')
  .add(fooQuery, { foo: 'some value' })
  .add(barQuery, { bar: 'another value' })

console.log(variables)
// { foo: 'some value', bar: 'another value' }

print(document)
/*
query FooBarQuery($foo: String!, $bar: String!) {
   getFoo(foo: $foo)
   getBar(bar: $bar)
}
*/

3

  • This seemed hopeful, but it's really fresh and has bugs. Not the least of which is the package name being misspelled in package.json.

    – ivanjonas

    Jul 16, 2020 at 13:28

  • @ivanjonas, what was the bug, other than package name?

    – Domas Lapinskas

    Jul 16, 2020 at 17:31

  • @DomasLapinskas Does this limit the network calls as well to just one ?

    – Shujath

    Feb 11, 2021 at 13:17


3

Now with graphql-request you can also do batching with queries:

import { batchRequests, gql } from 'graphql-request';

const bookQuery = gql`
    query book($title: String!) {
        book(title: $title) {
            title
        }
    }
`;

const endpoint = 'localhost/graphql/api/';

const books = await batchRequests(endpoint, [
      { document: bookQuery, variables: { title: 'Book 1' } },
      { document: bookQuery, variables: { title: 'Book 2' } },
    ]);

Then you’ll have books as a list of { data: book: { title } }. So it works with queries, mutation and duplicates.


0

Nobody mentioned another straightforward variant. It is possible to merge (combine) multiple gql queries into one with interpolation (credit to @maxdarque):


const fieldsOnBook = gql`
  fragment fieldsOnBook on Book {
    id
    author
  }
`

const fieldsOnCar = gql`
  fragment fieldsOnCar on Car {
    id
    name
  }
`

const bookQuery = gql`
  query ($bookId: ID!) {
    book(id: $bookId) {
      .... fieldsOnBook
    }
  }
 ${fieldsOnBook}
`
const carQuery = gql`
  query ($carId: ID!) {
    car(id: $carId) {
      ...fieldsOnCar
    }
  }
  ${fieldsOnCar}
`

const oneQuery = gql`
  query ($bookId: ID! $carId: ID!) {
    book(id: $bookId) {
      .... fieldsOnBook
    }
    car(id: $carId) {
      ... fieldsOnCar
    }
  }
  ${fieldsOnBook}
  ${fieldsOnCar}
`



Leave a Reply

Your email address will not be published. Required fields are marked *