Overview (simplified):
In my NodeJS server I've implemented the following GraphQL schema:
type Item {
name: String,
value: Float
}
type Query {
items(names: [String]!): [Item]
}
The client query then passes an array of names, as an argument:
{
items(names: ["total","active"] ) {
name
value
}
}
The backend API queries a mysql DB, for the "total" and "active" fields (columns on my DB table) and reduces the response like so:
[{"name":"total" , value:100} , {"name":"active" , value:50}]
I would like my graphQL API to support "ratio" Item, I.E: I would like to send the following query:
{
items(names: ["ratio"] ) {
name
value
}
}
or
{
items(names: ["total","active","ratio"] ) {
name
value
}
}
And return active / total as the calculated result of that new field ([{"name":"ratio" , value:0.5}]
). What would be a generic way to handle the "ratio" field differently?
Should it be a new type in my schema or should I implement the logic in the reducer?
Joe's answer (append {"name":"ratio" , value:data.active/data.total}
to the result once the result is fetched from database) would do it without making any schema changes.
As an alternative method or as a more elegant way to do it in GraphQL, the field names can be specified in the type itself instead of passing them as arguments. And compute ratio
by writing a resolver.
So, the GraphQL schema would be:
Item {
total: Int,
active: Int,
ratio: Float
}
type Query {
items: [Item]
}
The client specifies the fields:
{
items {
total
active
ratio
}
}
And ratio
can be calculated inside the resolver.
Here is the code:
const express = require('express');
const graphqlHTTP = require('express-graphql');
const { graphql } = require('graphql');
const { makeExecutableSchema } = require('graphql-tools');
const getFieldNames = require('graphql-list-fields');
const typeDefs = `
type Item {
total: Int,
active: Int,
ratio: Float
}
type Query {
items: [Item]
}
`;
const resolvers = {
Query: {
items(obj, args, context, info) {
const fields = getFieldNames(info) // get the array of field names specified by the client
return context.db.getItems(fields)
}
},
Item: {
ratio: (obj) => obj.active / obj.total // resolver for finding ratio
}
};
const schema = makeExecutableSchema({ typeDefs, resolvers });
const db = {
getItems: (fields) => // table.select(fields)
[{total: 10, active: 5},{total: 5, active: 5},{total: 15, active: 5}] // dummy data
}
graphql(
schema,
`query{
items{
total,
active,
ratio
}
}`,
{}, // rootValue
{ db } // context
).then(data => console.log(JSON.stringify(data)))
You could set your resolver function up so it uses the second parameter - the arguments - to see if the name "ratio" is in your names array:
resolve: (root, { names }, context, fieldASTs) => {
let arrayOfItems;
// Contact DB, populate arrayOfItems with your total / active items
// if 'ratio' is within your name array argument, calculate it:
if (names.indexOf("ratio") > -1){
// Calculate ratio
arrayOfItems.push({ name: "ratio", value: calculatedRatio });
}
return(arrayOfItems);
}
I hope I understood your question correctly
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With