What are good GraphQL query patterns? - graphql

What is a good pattern for GraphQL queries?
Consider the following (id and email are guaranteed unique).
First a pattern where the query name tells you what argument to expect:
getUserById(id)
getUserByEmail(email)
vs a single query that can handle either variable:
getUser({ id })
getUser({ email })
In this scenario, the resolver would pick the field and query by that.
Some of the pros/cons:
pro: using one query for both types enables more flexibility down the line to add more fields rather than creating new queries for each.
pro: using one query makes it easier to understand vs hunting different interfaces.
pro: if the consumer wanted to dynamically change the type you wouldn't have to write another query entirely.
con: validation would have to be handled manually in the resolver.
con: the backend complexity for the resolvers is increased.

I have some similar queries in a project and I tend to use the second pattern. Since I use the knex orm my resolver complexity doesn't increase. Just:
getUser: (parent,args,context) => knex('user').where(args).first()
Thanks to the GraphQL query definition I can depend on the args being limited to certain keys with specific types.
query getUser(id: ID, email: emailType): User
For args where the results are guaranteed unique this is fine. If the args might yield a non-unique answer then you may wish to handle them differently. There's also the issue of what to do if id and email don't point to the same record in your case.
Then of course since both id and email are optional there's the diminutive case where no argument is provided at all. GraphQL doesn't provide a way to specify at least one argument must be provided. Each argument is required or not individually.
You may want to handle this differently if you're not personally writing both the resolvers and the client side queries since random clients may send queries that violate your assumptions.

If you can have a query by id as the default pattern for all objects and for any special case there will be a descriptive query name.
This takes care of 90% of your need through the default pattern.
Just make an exception when you needed.
getUser(id)
getUserByEmail(email)
getCompany(id)
getCompanyByName(name)
#Query(() => UserDto, { description: 'Get other user info' })
async user(#UserDecorator() currentUser: User, #Args('id') id: string) {
return await this.prisma.user.findUnique({ where: { id } });
}
#Query(() => UserDto, { description: 'Get other user info by Email' })
async userByEmail(#UserDecorator() currentUser: User, #Args('email') email: string) {
return await this.prisma.user.findUnique({ where: { email } });
}
Alternatively, as mentioned already, pass in all variables as optional and let the backend handle it.
// everything is optional, and each unique
#InputType()
export class UserWhereUniqueInput implements Partial<User> {
#Field(() => ID, { nullable: true })
id?: string;
#Directive('#lowercase')
#Field(() => String, { nullable: true })
email?: string;
#Directive('#lowercase')
#Field(() => String, { nullable: true })
username?: string;
}
// on the backend, just let the ORM take the input as received
async user(userWhereUniqueInput: UserWhereUniqueInput): Promise<User | null> {
return this.prisma.user.findUnique({ where: userWhereUniqueInput });
}
Reference:
https://github.com/neekware/fullerstack
https://github.com/neekware/fullerstack/blob/916cf1031af0e077d339a3fbdad04eeb32fc3884/libs/nsx-user/src/lib/user.model.ts#L155
https://github.com/neekware/fullerstack/blob/916cf1031af0e077d339a3fbdad04eeb32fc3884/libs/nsx-user/src/lib/user.resolver.ts#L64
https://github.com/neekware/fullerstack/blob/916cf1031af0e077d339a3fbdad04eeb32fc3884/libs/nsx-user/src/lib/user.service.ts#L40

Related

Multiple queries in prisma graphql resolver

Following this example here:
https://github.com/prisma/prisma-examples/blob/latest/javascript/graphql-sdl-first/src/schema.js
Let's say I have a mutation where I want to update multiple users by passing their ids and emails accordingly. I know the updateMany would probably be the most suitable option, but since different users would have different values, not sure how to pass that without calling multiple resolvers separately. Something like this:
updateUsers: (_parent, args, context) => {
return context.prisma.user.updateMany({
where: {
id: { in: args.userIds },
},
data: {
email: ??? <--- use args.emails here
}
})
}
or should I just run multiple mutations:
updateUsers: async (_parent, args, context) => {
try {
args.emails.forEach(email => {
const user = await context.prisma.user.update({
where: {
id: { in: args.userIds },
},
data: {
email: args.email
}
})
return user;
}
} catch (error) {
console.log(error)
}
}
not sure if the last example would even work because of multiple return statements, since all the resolvers have a return statement, how can I run multiple queries/mutations?
updateMany allows you to bulk update all rows matching certain conditions with the same data. It does not help you in this case.
You could loop over an array of users to update id and email of each of them. Your approach will not work however, as you seem to have two arrays (a list of user IDs and a list of email addresses). However, instead of fixing your code I'd suggest to change the GraphQL interface you've defined.
Option 1) is close to what you did. You keep a bulk mutation updateUsers, but instead of two lists (a list of user IDs and a list of email addresses), it should accept a list of users, each of them having an user ID and an email address.
To improve performance you could use Promise.all and not wait for each update to happen, before starting the next one.
Option 2) is the preferable. Instead of a bulk mutation updateUsers, I would create a mutation updateUser that updates only one user. If client wants to update multiple users in the same request, they can! A single GraphQL request can contain multiple mutations.

Resolve to the same object from two incoherent sources in graphql

I have a problem I don't know how to solve properly.
I'm working on a project where we use a graphql server to communicate with different apis. These apis are old and very difficult to update so we decided to use graphql to simplify our communications.
For now, two apis allow me to get user data. I know it's not coherent but sadly I can't change anything to that and I need to use the two of them for different actions. So for the sake of simplicity, I would like to abstract this from my front app, so it only asks for user data, always on the same format, no matter from which api this data comes from.
With only one api, the resolver system of graphql helped a lot. But when I access user data from a second api, I find very difficult to always send back the same object to my front page. The two apis, even though they have mostly the same data, have a different response format. So in my resolvers, according to where the data is coming from, I should do one thing or another.
Example :
API A
type User {
id: string,
communication: Communication
}
type Communication {
mail: string,
}
API B
type User {
id: string,
mail: string,
}
I've heard a bit about apollo-federation but I can't put a graphql server in front of every api of our system, so I'm kind of lost on how I can achieve transparency for my front app when data are coming from two different sources.
If anyone has already encounter the same problem or have advice on something I can do, I'm all hear :)
You need to decide what "shape" of the User type makes sense for your client app, regardless of what's being returned by the REST APIs. For this example, let's say we go with:
type User {
id: String
mail: String
}
Additionally, for the sake of this example, let's assume we have a getUser field that returns a single user. Any arguments are irrelevant to the scenario, so I'm omitting them here.
type Query {
getUser: User
}
Assuming I don't know which API to query for the user, our resolver for getUser might look something like this:
async () => {
const [userFromA, userFromB] = await Promise.all([
fetchUserFromA(),
fetchUserFromB(),
])
// transform response
if (userFromA) {
const { id, communication: { mail } } = userFromA
return {
id,
mail,
}
}
// response from B is already in the correct "shape", so just return it
if (userFromB) {
return userFromB
}
}
Alternatively, we can utilize individual field resolvers to achieve the same effect. For example:
const resolvers = {
Query: {
getUser: async () => {
const [userFromA, userFromB] = await Promise.all([
fetchUserFromA(),
fetchUserFromB(),
])
return userFromA || userFromB
},
},
User: {
mail: (user) => {
if (user.communication) {
return user.communication.mail
}
return user.mail
}
},
}
Note that you don't have to match your schema to either response from your existing REST endpoints. For example, maybe you'd like to return a User like this:
type User {
id: String
details: UserDetails
}
type UserDetails {
email: String
}
In this case, you'd just transform the response from either API to fit your schema.

How to map query fields to database table column? [duplicate]

I am using graphql-tools. After receiving a GraphQL query, I execute a search using ElasticSearch and return the data.
However, usually the requested query includes only a few of the possible fields, not all. I want to pass only the requested fields to ElasticSearch.
First, I need to get the requested fields.
I can already get the whole query as a string. For example, in the resolver,
const resolvers = {
Query: {
async user(p, args, context) {
//can print query as following
console.log(context.query)
}
.....
}
}
It prints as
query User { user(id:"111") { id name address } }
Is there any way to get the requested fields in a format like
{ id:"", name:"", address:"" }
In graphql-js resolvers expose a fourth argument called resolve info. This field contains more information about the field.
From the GraphQL docs GraphQLObjectType config parameter type definition:
// See below about resolver functions.
type GraphQLFieldResolveFn = (
source?: any,
args?: {[argName: string]: any},
context?: any,
info?: GraphQLResolveInfo
) => any
type GraphQLResolveInfo = {
fieldName: string,
fieldNodes: Array<Field>,
returnType: GraphQLOutputType,
parentType: GraphQLCompositeType,
schema: GraphQLSchema,
fragments: { [fragmentName: string]: FragmentDefinition },
rootValue: any,
operation: OperationDefinition,
variableValues: { [variableName: string]: any },
}
In the fieldNodes field you can search for your field and get the selectionSet for the particular field. From here it gets tricky since the selections can be normal field selections, fragments or inline fragments. You would have to merge all of them to know all fields that are selected on a field.
There is an info object passed as the 4th argument in the resolver. This argument contains the information you're looking for.
It can be helpful to use a library as graphql-fields to help you parse the graphql query data:
const graphqlFields = require('graphql-fields');
const resolvers = {
Query: {
async user(_, args, context, info) {
const topLevelFields = graphqlFields(info);
console.log(Object.keys(topLevelFields)); // ['id', 'name', 'address']
},
};
Similarly for graphql-java you may do the same by extending the field parameters with myGetUsersResolverMethod(... DataFetchingEnvironment env).
This DataFetchingEnvironment would be injected for you and you can traverse through this DataFetchingEnvironment object for any part of the graph/query.
This Object allows you to know more about what is being fetched and what arguments have been provided.
Example:
public List<User> getUsers(final UsersFilter filter, DataFetchingEnvironment env) {
DataFetchingFieldSelectionSet selectionSet = env.getSelectionSet();
selectionSet.getFields(); // <---List of selected fields
selectionSet.getArguments(); // <--- Similarly but MAP
...
}
In fact you may be alluding to look ahead data fetching. The above should give you enough insights into the fields requested and you can take it from there to tailor you downstream calls manually. But also you may look into a more efficient way to do this by using the data fetchers for Building efficient data fetchers by looking ahead

Pass through GraphQL variables to second function in an elegant manner

I'm working with GraphQL and having some trouble finding the best way to pipe variables from the query to the result.
I have a schema like so:
type Fragment {
# The id of the fragment
id: String!
# The key of the fragment
key: String!
# The type of component
component_type: String!
# The params used to build the fragment
params: JSON
# Component data
data: JSON
children: [JSON]
items: [JSON]
}
The fragment is meant as a "cms" fragment. I want to pass some query data through to another backend after this resolves.
My query looks like this:
query getFragmentsWithItems($keys: [String!]!
$platform: PlatformType
$version: String
$userInfo: UserInput
$userId: Int
) {
fragmentsWithItems(keys: $keys, platform: $platform, version: $version, userInfo: $userInfo, userId: $userId) {
key
data
children
params
items
}
}
Here's the problem: I have some query data in the data field from the Fragment. That data is not available until that Fragment has resolved. I want to take that data and send it to a different backend. I want to do this with GraphQL, and I was hoping to do something like:
Fragment: () => {
async query(obj, args, context, info, {modles}) => {
const items = await models.getItems(obj.query_string);
}
}
But I need the user_info and user_id that I passed to the original query. Apparently that is only accessible from the info argument which is not meant to be used.
The other path I've taken is to have a manual resolver that does something like so:
const resolveFI = ({ keys, platform, version, userInfo, userId, models }) => {
if (!keys || !keys.length) {
return Promise.resolve(null);
}
return models.release.get({ platform, version }).then(release =>
Promise.all(
keys.map(key =>
models.fragments.get({
key,
platform,
version,
release: release.id
})
)
).then(data => {
const promises = [];
data.rows.forEach(r => {
if (r.data.query_data) {
const d = {
// Can just ignore
filters: r.data.query_data.filters || {},
user_info: userInfo,
user_id: userId
};
promises.push(
new Promise(resolve => {
resolve(
models.itemSearch.get(d).then(i => ({ items: i.items, ...r }))
);
})
);
}
...etc other backends
This works, however a manual promise chain seems to defeat the purpose of using GraphQL.
The last thing I tried was making items a non-scalar type, something like:
type Fragment {
items: ItemSearchResult(user_info: UserInput) etc
But since I can't pipe the actual result from Fragment to the ItemSearchResult that doesn't work.
I realize this is pretty long-winded so I'm open to edits or clarifying.
I'm looking to see if I've missed a better approach or if I should just bag it and have the client apps do the item query after they get the Fragment data back.
It's not that you're not supposed to use info -- it's just a tremendous pain in the butt to use ;) In all seriousness, it's meant to be used for optimization and more advanced use cases, so you shouldn't hesitate to use it if a better solution doesn't present itself. There are libraries out there (like this one) that you can use to parse the object more easily.
That said, there's a couple of ways I imagine you could handle this:
1.) Inside your query resolver(s)
getFragmentsWithItems: async (obj, args, ctx, info) => {
const fragments = await howeverYouDoThat()
const backendCalls = fragments.map(fragment => {
// extract whatever data you need from the fragment
return asyncCallToBackEnd()
})
await backendCalls
return fragments
}
Unfortunately, if you have a lot of different queries returning fragments, you'll end up with redundancy.
2.) Inside the resolver for an existing field (or an additional one) on the Fragment type.
If you go this route, and you need args passed to the query field, you can extract them using the info. Alternatively, you can also mutate the context object inside your query resolver and attach those arguments to it. Then, all resolvers "below" the query resolver (like the resolvers for your Fragment fields) can access those arguments through the context.
3.) Apollo Server lets you define a formatResponse function when configuring its middleware. This essentially provides a hook to do whatever you want with the response before it's returned to the client. You could parse the response inside that function and make the calls to the other backend from there.

can some one explain this code to me

Good day im newbie here and im tackling graphql and im having some problem on mutation can someone explain this block of code for me thank you
RootMutation: {
createAuthor: (root, args) => { return Author.create(args); },
createPost: (root, { authorId, tags, title, text }) => {
return Author.findOne({ where: { id: authorId } }).then( (author) => {
console.log('found', author);
return author.createPost( { tags: tags.join(','), title, text });
});
},
},
Sure, this is an example of two mutations in a GraphQL server. We can break it down to understand what is going on.
First let's look at the type system. A GraphQL schema normally has two root fields query and mutation (and sometimes subscription). These root fields are the root of your data hierarchy and expose the queries (GET requests) and mutations (POST, PUT, DELETE, etc requests) that you have access to.
By the looks of it you are implementing a schema with a root mutation type that looks like this:
type Mutation {
createAuthor: Author
createPost: Post
}
A type in GraphQL is made up of a set of fields each of which can have an associated resolver. Resolvers in GraphQL are like the event handlers you would attach to endpoints in REST.
The code that you have above is defining two resolvers that will handle the logic associated with the createAuthor and createPost mutations. I.E. the code in the createPost resolver is what will be run when I issue a query like this:
mutation CreatePost($post: CreatePostInput!) {
createPost(input: $post) {
id
title
tags
text
}
}
The GraphQL runtime parses the query and routes the operation to the correct resolver depending on the content of the query. In this example, it would see that I am calling the createPost mutation and would make sure to call the createPost resolver which in your case looks like this:
createPost: (root, { authorId, tags, title, text }) => {
return Author.findOne({ where: { id: authorId } }).then( (author) => {
console.log('found', author);
return author.createPost( { tags: tags.join(','), title, text });
});
},
To understand how a resolver works, let's look at the GraphQLFieldResovler type definition from graphql-js
export type GraphQLFieldResolver<TSource, TContext> = (
source: TSource,
args: { [argName: string]: any },
context: TContext,
info: GraphQLResolveInfo
) => mixed;
As you can see a GraphQLFieldResolver is a function that takes 4 arguments.
source: The source is the parent object of the current field. For example if you were defining a resolver for a field fullName on the User type, the source would be the full user object.
args: The args are any input arguments for that resolver. In my query above it would contain the value of the $post variable.
context: Context is a global context for a GraphQL execution. This is useful for passing information around that a resolver might need. For example, you include a database connection that you can use from your resolvers without importing it in every file.
info: The info object contains information about your GraphQL schema, the query, and other information such as the path to the current resolver being executed. This is useful in many ways. Here is one post talking about how you can use it to precompute queries: (https://scaphold.io/community/blog/querying-relational-data-with-graphql/)
This idea of having types and field resolvers is part of what makes GraphQL so powerful. Once you've defined you type system and the resolvers for their fields you can structure your schema however you want and GraphQL will always make sure to call the correct resolver no matter how deeply nested a query might be.
I hope this helps :)

Resources