Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Merging 2 REST endpoints to a single GraphQL response

New to graphQL, I'm Using the following schema:

type Item {
    id: String,
    valueA: Float,
    valueB: Float
  }

  type Query {
    items(ids: [String]!): [Item]
  }

My API can return multiple items on a single request of each type (A & B) but not for both, i.e:

REST Request for typeA : api/a/items?id=[1,2]
Response:

[
  {"id":1,"value":100},
  {"id":2,"value":30}
]

REST Request for typeB : api/b/items?id=[1,2]
Response:

[
  {"id":1,"value":50},
  {"id":2,"value":20}
]

I would like to merge those 2 api endpoints into a single graphQL Response like so:

[
  {
    id: "1",
    valueA: 100,
    valueB: 50
   },
   {
    id: "2",
    valueA: 30,
    valueB: 20
    }
 ]

Q: How would one write a resolver that will run a single fetch for each type (getting multiple items response) making sure no unnecessary fetch is triggered when the query is lacking the type i.e:

{items(ids:["1","2"]) {
  id
  valueA
}}

The above example should only fetch api/a/items?id=[1,2] and the graphQL response should be:

[
  {
    id: "1",
    valueA: 100
  },
  {
    id: "2",
    valueA: 30
  }
]
like image 450
Shlomi Schwartz Avatar asked Nov 01 '17 11:11

Shlomi Schwartz


1 Answers

So I assumed you are using JavaScript as the language. What you need in this case is not to use direct query, rather use fragments

So the query would become

{
    items(ids:["1","2"]) {
       ...data
    }}

    fragment data on Item {
        id
      valueA
    }
}

Next in the resolver we need to access these fragments to find the fields which are part of the fragment and then resolve the data based on the same. Below is a simple nodejs file with same

const util = require('util');

var { graphql, buildSchema } = require('graphql');

var schema = buildSchema(`
    type Item {
        id: String,
        valueA: Float,
        valueB: Float
    }

    type Query {
        items(ids: [String]!): [Item]
    }
`);

var root = { items: (source, args, root) => {
        var fields = root.fragments.data.selectionSet.selections.map(f => f.name.value);
        var ids = source["ids"];

        var data = ids.map(id => {return {id: id}});
        if (fields.indexOf("valueA") != -1)
        {
            // Query api/a/items?id=[ids]
            //append to data;
            console.log("calling API A")
            data[0]["valueA"] = 0.12;
            data[1]["valueA"] = 0.15;
        }

        if (fields.indexOf("valueB") != -1)
        {
            // Query api/b/items?id=[ids]
            //append to data;
            console.log("calling API B")
            data[0]["valueB"] = 0.10;
            data[1]["valueB"] = 0.11;
        }
        return data
},
};

graphql(schema, `{items(ids:["1","2"]) {
       ...data
    }}

    fragment data on Item {
        id
      valueA
    }

    `, root).then((response) => {
    console.log(util.inspect(response, {showHidden: false, depth: null}));
});

If we run it, the output is

calling API A
{ data: 
   { items: [ { id: '1', valueA: 0.12 }, { id: '2', valueA: 0.15 } ] } }

If we change the query to

{
    items(ids:["1","2"]) {
       ...data
    }}

    fragment data on Item {
        id
        valueA
        valueB
    }
}

The output is

calling API A
calling API B
{ data: 
   { items: 
      [ { id: '1', valueA: 0.12, valueB: 0.1 },
        { id: '2', valueA: 0.15, valueB: 0.11 } ] } }

So this demonstrates how you can avoid call for api A/B when their fields are not needed. Exactly as you had asked for

like image 55
Tarun Lalwani Avatar answered Sep 27 '22 18:09

Tarun Lalwani