4
votes

What can be the best way to resolve the data in GraphQL

Here i have a SeekerType and JobType, JobsType is nested in SeekerType

A Seeker can apply to many Jobs. When Querying for a seeker, One can just query for seeker's data or as well as he can query for nested JobType and can get the jobstype data too.

But the Question is that If One doesn't Query for nested JobType he won't get the Jobs data but mine Seeker resolver in viewerType would be fetching that data too.

So, while providing data to the seeker query how can i handle that, Either he can only want seeker details or may want the jobs details too.

Shall I use resolver of each nestedType and get the parent object, and fetch the relevant data using fields from parent Object???

The code below is just for illustration and clarification, the question is about the best way to resolve data

ViewerType.js

const Viewer = new GraphQLObjectType({
    name: 'Viewer',
    fields: () => ({
        Seeker: {
            type: SeekerConnection,
            args: _.assign({
                seekerId: { type: GraphQLID },
                status: { type: GraphQLString },
                shortlisted: { type: GraphQLInt },
            }, connectionArgs),
            resolve: (obj, args, auth, rootValue) => {
                const filterArgs = getFilters(args) || {};
                return connectionFromPromisedArray(getSeekers(filterArgs), args)
                    .then((data) => {

      // getSeekers() provides all the data required for SeekerType fields and it's
          JobsType fields

                    data.args = filterArgs;
                    return data;
                }).catch(err => new Error(err));
            },
        },
    }),
});

SeekerType.js

const SeekerType = new GraphQLObjectType({
    name: 'SeekerType',
    fields: () => ({
        id: globalIdField('SeekerType', obj => obj._id),
        userId: {
            type: GraphQLID,
            resolve: obj => obj._id,
        },
        email: { type: GraphQLString },
        password: { type: GraphQLString },
        firstName: { type: GraphQLString },
        lastName: { type: GraphQLString },
        imageLink: { type: GraphQLString },
        education: { type: GraphQLString },
        address: { type: GraphQLString },
        jobs: {
            type: new GraphQLList(JobType),
        },
    }),
    interfaces: [nodeInterface],
});

getSeekers() provide complete data as graphql fields format with nested jobs field data too

const getSeekers = filterArgs => new Promise((resolve, reject) => {
    if (Object.keys(filterArgs).length === 0) {
        Seeker.find(filterArgs, { password: 0 }, (err, d) => {
            if (err) return reject(err);
            return resolve(d);
        });
    } else {
        async.parallel([
            (callback) => {
                filterArgs._id = filterArgs.seekerId;
                delete filterArgs.seekerId;
                Seeker.find(filterArgs).lean()
                       .exec((err, d) => {
                    if (err) return callback(err);
                    if (err === null && d === null) return callback(null);
                    callback(null, d);
                });
            },
            (callback) => {
                filterArgs.seekerId = filterArgs._id;
                delete filterArgs._id;
                Applicant.find(filterArgs).populate('jobId').lean()
                    .exec((err, resp) => {
                    if (err) return callback(err);
                    callback(null, resp);
                });
            },
        ], (err, data) => {
            const cleanedData = {
                userData: data[0],
                userJobMap: data[1],
            };
            const result = _.reduce(cleanedData.userData, (p, c) => {
                if (c.isSeeker) {
                    const job = _.filter(cleanedData.userJobMap, 
                                 v => _.isEqual(v.seekerId, c._id));
                    const arr = [];
                    _.forEach(job, (i) => {
                        arr.push(i.jobId);
                    });
                    const t = _.assign({}, c, { jobs: arr });
                    p.push(t);
                    return p;
                }
                return reject('Not a Seekr');
            }, []);
            if (err) reject(err);
            resolve(result);

            // result have both SeekerType data and nested type 
               JobType data too.


        });
    }
});
2

2 Answers

1
votes

I gather this to be a question about how to prevent overfetching related data...I.e. How not to necessarily request jobs data when querying the seeker.

This might have several motivations for optimization and security.

Considerations:

  1. If the consumer (e.g. Web app) is under your control, you can simply avoid requesting the jobs field when querying seeker. As you may know, this is one of the stated goals of graphql to only return what is needed over the wire to the consumer, to minimize network traffic and do things in one trip. On the backend I would imagine the graphql engine is smart enough not to overfetch jobs data either, if it's not requested.

  2. If your concern is more of security or unintentional overfetching by consumer apps out of your control, for example, then you can solve that by creating seperate queries and limiting access to them even. E.g. One query for seeker and another for seekerWithJobsData.

  3. Another technique to consider is graphql directives which offer an include switch that can be used to conditionally serve specific fields. One advantage of using this technique in your scenario might be to allow a convenient way to conditionally display multiple fields in multiple queries depending on the value of a single boolean e.g. JobSearchFlag=false. Read here for an overview of directives: http://graphql.org/learn/queries/

0
votes

I am not sure I completely understand the question but it seems to me you're loading both seeker and job types info on one level. You should load both of them on demand.

On the seeker level, you only get the seeker information, and you can get the IDs of any records related to that seeker. For example, job types ids (if a seeker has many job types)

On the job type level, when used as a nested level for one seeker, you can use those ids to fetch the actual records. This would make the fetching of job types record on-demand when the query asks for it.

The ID to record fetching can be cached and batched with a library like dataloader