How to define and execute GraphQL query with filter - graphql

So the thing i try to do is, retrieve filtered data from the Database (MongoDB in my situation) using GraphQL.
Speaking in "MySQL language" how to implement the where clause in GraphQL?
I followed this tutorial :
https://learngraphql.com/basics/using-a-real-data-source/5
Query with filter was defined like this :
const Query = new GraphQLObjectType({
name: "Queries",
fields: {
authors: {
type: new GraphQLList(Author),
resolve: function(rootValue, args, info) {
let fields = {};
let fieldASTs = info.fieldASTs;
fieldASTs[0].selectionSet.selections.map(function(selection) {
fields[selection.name.value] = 1;
});
return db.authors.find({}, fields).toArray();
}
}
}
});
The tricky part here is the info parameter in the resolve function. Some explanations i've found here : http://pcarion.com/2015/09/26/graphql-resolve/
So it is AST (Abstract Syntax Tree)
Can anyone please provide some basic real-life example code that would show how to define and execute the following query :
Get all authors where name == John
Thank you!

There is no need to examine the AST. That would be awfully laborious.
All you need to do is define an argument on the author field. This is the second paramenter of the resolver, so you can check for that arguement and include it in the Mongo query.
const Query = new GraphQLObjectType({
name: "Queries",
fields: {
authors: {
type: new GraphQLList(Author),
// define the arguments and their types here
args: {
name: { type: GraphQLString }
},
resolve: function(rootValue, args, info) {
let fields = {};
// and now you can check what the arguments' values are
if (args.name) {
fields.name = args.name
}
// and use it when constructing the query
return db.authors.find(fields, fields).toArray();
}
}
}
});

Related

Pass parent resolver arguments in child resolver

I have a nested GraphQL query with the following structure
query{
parentMetrics(cityNames: ["Cali"], orderingDateFrom: "2021-01-01"){
sales
orders
childMetrics(cityNames: ["Cali"], orderingDateFrom: "2021-01-01"){
aov
}
}
}
As you can see I am repeating the arguments because in the backend we run different queries to get parentMetrics and childMetrices but they require the same set of args which is redundant.
Can I do something like this instead?
query{
parentMetrics(cityNames: ["Cali"], orderingDateFrom: "2021-01-01"){
sales
orders
childMetrics{
aov
}
}
}
I am using github.com/graphql-go/graphql and currently this is what my code looks like
"parentMetrics": &graphql.Field{
Type: partnerGQL.ParentMetrics,
Args: graphql.FieldConfigArgument{
"cityNames": &graphql.ArgumentConfig{
Type:graphql.NewList(graphql.String),
}
"orderingDateFrom":&graphql.ArgumentConfig{
Type: graphql.String,
}
},
Resolve: partnerResolver.ResolveOrderItemMetrics,
}
The parentMetrics type has the nested Resolver for childMetrics
When you return a parent metric object, include the arguments of parentMetrics call in that object.
The resolver for childMetrics will get the parent object and will be able to read its arguments from there.
A quick example in Javascript:
const resolvers = {
parentMetrics: (parent, args) => {
const metric = getParentMetric(args);
return {
...metric,
args: args,
};
},
childMetrics: (parent) => {
return getChildMetric(parent.args);
}
}

How to implement mutations with optional arguments in GraphQL?

I am learning about graphql, and went through the https://www.howtographql.com/graphql-js/3-a-simple-mutation/ tutorial, and was interested in what the implementation of the updateLink mutation as follows would look like.
type Query {
# Fetch a single link by its `id`
link(id: ID!): Link
}
type Mutation {
# Update a link
updateLink(id: ID!, url: String, description: String): Link
}
The reason I am asking this is that every other mutation implementation I have seen uses only NON-optional parameters. I am curious if there is a community-agreed-upon pattern for extracting and applying only the provided non-null arguments(url, description) from the given context and applying them to relevant the database record.
I have considered checking if each variable is null as follows, but this approach looks way messier than I would expect compared to the rest of the 'magic' and simplicity that Graphql provides.
updateLink(root, args, context) {
if (args.url == null && args.description == null){
return null
} else if (args.url == null) {
return context.prisma.updateLink({
id: args.id,
description: args.description
})
} else {
return context.prisma.updateLink({
id: args.id,
url: args.url
})
}
}
Please let me know if you found a cleaner way to extract and apply the optional arguments(url, description).
Another consideration I had was to make two separate update mutations as follows.
type Query {
# Fetch a single link by its `id`
link(id: ID!): Link
}
type Mutation {
# Update a link
updateLinkURL(id: ID!, url: String!): Link
updateLinkDescription(id: ID!, description: String!): Link
}
The thinking here was with limited arguments and a declarative mutation name, one could force the arguments to be Non-Null. The main issue here is that one can have many update methods for tables with many columns, this would also start to look messy.
FYI I am using prisma as my ORM.
const resolvers = {
Query: {
info: () => `This is the API of a Hackernews Clone`,
feed: () => links,
link: (parent, args) => {
// console.log(args)
return links.find((link) => link.id === args.id)
}
},
Link: {
id: (parent) => parent.id,
description: (parent) => parent.description,
url: (parent) => parent.url,
},
}

Unable to use Fragments on GraphQL-yoga with Primsa

I am using graphql-yoga with Prisma and Prisma-Bindings
I'm trying to add a fragment to my resolver so that a specific field (id in this situation) is always fetched when the user asks for a custom field, costsToDate.
This is so i can make some additional queries needed to build the result for that field, and i need the ID of the object for that.
Unfortunatley i can't seem to get it to work, and the documentations seems a little lacking on the specifics with graphql-yoga and Prisma.
Here is the definition of the type:
type Job {
id: ID!
projectNumber: String!
client: Client!
name: String!
description: String
quoteNumber: String
workshopDaysQuoted: String!
quoted: String!
targetSpend: String!
costs: [JobCost!]!
estimatedCompletion: DateTime
completed: Boolean!
costTotal: String
workshopDaysUsed: String
costsToDate: String
}
And here is the resolver for the query:
const jobs = {
fragment: `fragment description on Jobs { id }`,
resolve: jobsResolver
}
async function jobsResolver(root, args, context, info) {
await validatePermission(args,context,info,['admin','user','appAuth'])
const {showCompleted} = args
const completedFilter = (typeof showCompleted === 'boolean') ? { completed: showCompleted } : {}
const jobIDS = await context.db.query.jobs({ where: completedFilter }, `{ id }`)
//console.log(jobIDS);
let jobs = await context.db.query.jobs({
where: completedFilter
}, info)
return await getAllJobCostsToDateList(jobs)
}
I am applying the the fragmentReplacements as per below.
const fragmentReplacements = extractFragmentReplacements(resolvers)
console.log(fragmentReplacements)
const port = process.env.PORT || 3010
const graphQLServer = new GraphQLServer({
typeDefs: './src/schema.graphql',
resolvers,
resolverValidationOptions: {
requireResolversForResolveType: false
},
context: req => ({
...req,
db: new Prisma({
typeDefs: `src/generated/prisma.graphql`,
fragmentReplacements,
endpoint: PRISMA_ENDPOINT,
secret: PRISMA_KEY,
debug: false
})
})
})
If i console.log the fragmentReplacements object i get the following, so it does seem to be picking up the fragments.
[ { field: 'job', fragment: 'fragment costsToDate on Job { id }' },
{ field: 'jobs',
fragment: 'fragment costsToDate on Jobs { id }' } ]
So my expectation here is that if i make a query against jobs or job that asks for the costsToDate field that it will also fetch the id for the job/each job.
However if i make the following query.
query{
jobs{
description
costsToDate
}
}
But i see no id fetched, and nothing in the root parameter on the resolver function.
Apologies as i am probably barking up completely the wrong tree here, seems like a somewhat simple requirement, but i can't quite work it out. Sure i'm missing something fundamental.
Thanks!
Gareth
A fragment is used to always retrieve given fields on a given type.
It follows the following format:
fragment NameOfYourFragment on YourType { ... }
You currently can't apply a given fragment conditionally as it is always applied.
Moreover, you specified a fragment on Jobs, but the type name used by Prisma is Job (even if you have the job and jobs resolvers)
You probably only need the following resolver:
const job = {
fragment: `fragment JobId on Job { id }`,
resolve: jobsResolver
}

How to create generics with the schema language?

Using facebook's reference library, I found a way to hack generic types like this:
type PagedResource<Query, Item> = (pagedQuery: PagedQuery<Query>) => PagedResponse<Item>
​
interface PagedQuery<Query> {
query: Query;
take: number;
skip: number;
}
​
interface PagedResponse<Item> {
items: Array<Item>;
total: number;
}
function pagedResource({type, resolve, args}) {
return {
type: pagedType(type),
args: Object.assign(args, {
page: { type: new GraphQLNonNull(pageQueryType()) }
}),
resolve
};
function pageQueryType() {
return new GraphQLInputObjectType({
name: 'PageQuery',
fields: {
skip: { type: new GraphQLNonNull(GraphQLInt) },
take: { type: new GraphQLNonNull(GraphQLInt) }
}
});
}
function pagedType(type) {
return new GraphQLObjectType({
name: 'Paged' + type.toString(),
fields: {
items: { type: new GraphQLNonNull(new GraphQLList(type)) },
total: { type: new GraphQLNonNull(GraphQLInt) }
}
});
}
}
But I like how with Apollo Server I can declaratively create the schema. So question is, how do you guys go about creating generic-like types with the schema language?
You can create an interface or union to achieve a similar result. I think this article does a good job explaining how to implement interfaces and unions correctly. Your schema would look something like this:
type Query {
pagedQuery(page: PageInput!): PagedResult
}
input PageInput {
skip: Int!
take: Int!
}
type PagedResult {
items: [Pageable!]!
total: Int
}
# Regular type definitions for Bar, Foo, Baz types...
union Pageable = Bar | Foo | Baz
You also need to define a resolveType method for the union. With graphql-tools, this is done through the resolvers:
const resolvers = {
Query: { ... },
Pageable {
__resolveType: (obj) => {
// resolve logic here, needs to return a string specifying type
// i.e. if (obj.__typename == 'Foo') return 'Foo'
}
}
}
__resolveType takes the business object being resolved as its first argument (typically your raw DB result that you give GraphQL to resolve). You need to apply some logic here to figure out of all the different Pageable types, which one we're handling. With most ORMs, you can just add some kind of typename field to the model instance you're working with and just have resolveType return that.
Edit: As you pointed out, the downside to this approach is that the returned type in items is no longer transparent to the client -- the client would have to know what type is being returned and specify the fields for items within an inline fragment like ... on Foo. Of course, your clients will still have to have some idea about what type is being returned, otherwise they won't know what fields to request.
I imagine creating generics the way you want is impossible when generating a schema declaratively. To get your schema to work the same way it currently does, you would have to bite the bullet and define PagedFoo when you define Foo, define PagedBar when you define Bar and so on.
The only other alternative I can think of is to combine the two approaches. Create your "base" schema programatically. You would only need to define the paginated queries under the Root Query using your pagedResource function. You can then use printSchema from graphql/utilities to convert it to a String that can be concatenated with the rest of your type definitions. Within your type definitions, you can use the extend keyword to build on any of the types already declared in the base schema, like this:
extend Query {
nonPaginatedQuery: Result
}
If you go this route, you can skip passing a resolve function to pagedResource, or defining any resolvers on your programatically-defined types, and just utilize the resolvers object you normally pass to buildExecutableSchema.

Can you make a graphql type both an input and output type?

I have some object types that I'd like to use as both input and output - for instance a currency type or a reservation type.
How do I define my schema to have a type that supports both input and output - I don't want to duplicate code if I don't have to. I'd also prefer not to create duplicate input types of things like currency and status enums.
export const ReservationInputType = new InputObjectType({
name: 'Reservation',
fields: {
hotelId: { type: IntType },
rooms: { type: new List(RoomType) },
totalCost: { type: new NonNull(CurrencyType) },
status: { type: new NonNull(ReservationStatusType) },
},
});
export const ReservationType = new ObjectType({
name: 'Reservation',
fields: {
hotelId: { type: IntType },
rooms: { type: new List(RoomType) },
totalCost: { type: new NonNull(CurrencyType) },
status: { type: new NonNull(ReservationStatusType) },
},
});
In the GraphQL spec, objects and input objects are distinct things. Quoting the spec for input objects:
Fields can define arguments that the client passes up with the query, to configure their behavior. These inputs can be Strings or Enums, but they sometimes need to be more complex than this.
The Object type... is inappropriate for re‐use here, because Objects can contain fields that express circular references or references to interfaces and unions, neither of which is appropriate for use as an input argument. For this reason, input objects have a separate type in the system.
An Input Object defines a set of input fields; the input fields are either scalars, enums, or other input objects. This allows arguments to accept arbitrarily complex structs.
While an implementation might provide convenience code to create an object and a corresponding input object from a single definition, under the covers, the spec indicates that they'll have to be separate things (with separate names, such as Reservation and ReservationInput).
While working on a project I had a similar problem with code duplication between input and type objects. I did not find the extend keyword very helpful as it only extended the fields of that specific type. So the fields in type objects cannot not be inherited in input objects.
In the end I found this pattern using literal expressions helpful:
const UserType = `
name: String!,
surname: String!
`;
const schema = graphql.buildSchema(`
type User {
${UserType}
}
input InputUser {
${UserType}
}
`)
You can do something like this:
export const createTypes = ({name, fields}) => {
return {
inputType: new InputObjectType({name: `${name}InputType`, fields}),
objectType: new ObjectType({name: `${name}ObjectType`, fields})
};
};
const reservation = createTypes({
name: "Reservation",
fields: () => ({
hotelId: { type: IntType },
rooms: { type: new List(RoomType) },
totalCost: { type: new NonNull(CurrencyType) },
status: { type: new NonNull(ReservationStatusType) }
})
});
// now you can use:
// reservation.inputType
// reservation.objectType
this is something that i did for my project (works good):
const RelativeTemplate = name => {
return {
name: name,
fields: () => ({
name: { type: GraphQLString },
reference: { type: GraphQLString }
})
};
};
const RelativeType = {
input: new GraphQLInputObjectType(RelativeTemplate("RelativeInput")),
output: new GraphQLObjectType(RelativeTemplate("RelativeOutput"))
};

Resources