Skip to main content

Automatic Resolver Filtering with Wrappers

A common pattern is to filter information on Field Resolvers based on arguments from a query. In the case this pattern can be applied to multiple Field Resolvers it's useful to standardize this filtering with Resolver Wrappers. This helps separate filtering from the data resolution and allows for re-use. The nature of the filtering depends on the conventions of your GraphQL API.

Example

This Resolver Wrapper is an example of automatically filtering a list based on the arguments passed in. If a given key is provided it will filter any list down based on the value of the argument.

import { createWrapper, WrapperFor } from "graphql-mocks/resolver";

function filterResult(result, args) {
// only filter on results that are arrays of objects, ie: [User!]!
if (
!Array.isArray(result) ||
(Array.isArray(result) && result.some((item) => typeof item !== "object"))
) {
return result;
}

const filtered = result.filter((resultItem) => {
// ensure that the value for an argument matches the value
// on the object for the matching key
return Object.entries(args).every(([key, value]) => {
if (key in resultItem) {
return resultItem[key] === value;
}

return true;
});
});

return filtered;
}

export const automaticFilterWrapper = createWrapper(
"automatic-field-filtering",
WrapperFor.FIELD,
async function (originalResolver, wrapperOptions) {
return async (parent, args, context, info) => {
const result = await originalResolver(parent, args, context, info);
return filterResult(result, args);
};
}
);

For this example the Query.movies resolver has the following data:

export const movies = [
{
title: "Spirited Away",
year: "2001",
},

{
title: "Shrek",
year: "2001",
},

{
title: "La La Land",
year: "2016",
},
];

Then setting up the handler to apply the automaticFilter Wrapper against the Query.movies field resolver. The highlight argument can be customized to specify a specific selection of fields that should receive the automatic filter wrapper. Also, because there can be multiple Resolver Wrappers passed into the wrappers array, you could crete and combine multiple automatic filtering Field Wrappers to flexibly compose filtering across your fields.

The final query in the example, FilteredMovies, will automatically filter based on the query args { "year": "2001" } since year is an argument that also exists on the Movie type being filtered.

import { GraphQLHandler } from "graphql-mocks";
import { embed } from "graphql-mocks/resolver-map";
import { automaticFilterWrapper } from "./automatic-filtering-filter.source";
import { resolverMap } from "./resolver-map";

const handler = new GraphQLHandler({
resolverMap,
middlewares: [
embed({
wrappers: [automaticFilterWrapper],
}),
],

dependencies: {
graphqlSchema: `
schema {
query: Query
}

type Query {
movies(title: String, year: String): [Movie!]!
}

type Movie {
title: String!
year: String!
}
`,
},
});

const query = handler.query(
`
query FilteredMovies($year: String) {
movies(year: $year) {
title
year
}
}
`,
{
year: "2001",
}
);
query.then((result) => console.log(result));
Result:
{
  "data": {
    "movies": [
      {
        "title": "Spirited Away",
        "year": "2001"
      },
      {
        "title": "Shrek",
        "year": "2001"
      }
    ]
  }
}

In the result the movie "La La Land" was filtered out since it was released in 2016 and the query specified movies with the year '2001'.