graphql resolver optimisation - graphql

If I have the schema:
type Query {
posts: [Post!]!
}
type Post {
title: String!
lotsofdata: String
}
and a resolver:
function posts(parent, args, context, info) {
return readAllPosts(/*?*/)
}
And two possible queries. Query #1:
query {
posts{
title
}
}
and query #2:
query {
posts{
title
lotsofdata
}
}
Is it possible to optimise the resolver so with query #1 readAllPosts only pulls back titles from the database but for query #2 it pulls back both titles and lotsofdata?
I've looked at the parent, args, context, and info arguments but can't see anything to indicate whether the resolver is being called in response to a query like #1 or like #2.

not sure if it is still relevant for you, but it should be possible, you can take a look at library called https://github.com/robrichard/graphql-fields#readme. It will parse info argument in your resolver function. This way you can gain information about executed fields into your resolver. The other part is to use this information to build proper SQL statement or projection or whatever (dependent on what db you use). I hope, that it helps.
Best David

Related

Passing variables in GraphQL

I'm trying to run a GraphQL query in the AWS AppSync console:
query MyQuery {
getUserInfoById(id: "1234566789") {
account {
id // need this value for getAvailableCourses
}
}
getAvailableCourses(accountId: "", pageNumber: 0) {
data {
id
name
type
}
}
}
Basically I need the value account.id in getUserInfoById for getAvailableCourses. I'm obviously new to GraphQL. How would I go about this?
To the best of my knowledge, there can be two ways you can do this.
You can handle this in your frontend by getting user's id
from the session info and pass it to the other query.
You can also merge these two queries and make it one. You will also have to change the respective fields. Then attach a resolver with AvailableCourses and use $ctx.source.id in the resolver to get further details. Schema would look something like this
type Account {
id : ID!
availableCourses: AvailableCourses
..
}
type AvailableCourses {
name: String!
type: String!
..
}
type Query {
getUserInfoById(id: ID!): Account
}
Using the returned fields as inputs for a second query into your datasource is precisely what field resolvers are for. I can't say for sure since I don't know your schema or access patterns but it looks like you need to make available courses a sub field of the user.

How to approach a GraphQL query that returns a boolean value?

Need to check whether an email is available or taken during the user sign-up process. The goal is to quickly query, using GraphQL, the API server and have it tell us if the email is available or taken.
What is the general best practice on a simple boolean-ish type of situation using GraphQL?
Below is what I have come up with but I am unsure if this is a good practice or not and want to hear feedback on a better practice on queries like this.
Request:
query {
emailExists(email:"jane#doe.com") {
is
}
}
Response:
{
"data": {
"emailExists": {
"is": true
}
}
}
A "query" is just a field on what happens to be the Query type. A field can return any output type, including scalars -- it doesn't need to return an object. So it's sufficient to have a schema like:
type Query {
emailExists(email: String!): Boolean!
}
The only reason to prefer an object type would be if you anticipated wanting to add additional fields in the future (i.e. something other than your current is field).

In GraphQL, how to control DB query by client query?

I learn to use GraphQL these days. In my opinion, To build a query, I need to build three parts:
Schema
type User{
id String
name String
cars [Car!]!
}
type Car{
id String
}
type Query{
user(id: String): User
}
DB Query function
{
user: async function ({id}) {
const user = await DB.user.findOne({id});
const userCars = await DB.car.find({userId: id});
user.cars = userCars;
return cars;
}
}
Client query
{
user (id: "1") {
name
cars {
id
}
}
}
That query returns a user's name and his cars. The DB query function always query for cars.
But sometimes I just need user's info:
{
user (id: "1") {
name
}
}
I don't want to query for cars, so I hope to make my DB query function can auto choose to query for cars or not.
How can I do this?
GraphQL.js will support either object properties or methods for resolver functions; this is discussed in its page on Object Types.
One way to deal with this is just to insert an anonymous function directly into the returned object:
{
user: async function ({id}) {
const user = await DB.user.findOne({id});
user.cars = () => DB.car.find({userId: id});
return cars;
}
}
Another is to create a wrapper object with a class that provides the id property and (asynchronous, lazy) cars method; some examples of this are in the GraphQL.js documentation. This approach tends to work in most GraphQL implementations in most languages.
I think you looking into auto-creating/mapping from GraphQL query into db query.
Every queries are db/project specific, so you should create this mapping. You can easily do that with graphql-fields package.
There is copy pasted WHY section from the package:
An underlying REST api may only return fields based on query params.
{
user {
profile {
firstName
},
id
}
}
should request /api/user?fields=profile,id
while
{
user {
email
}
}
should request /api/user?fields=email
Implement your resolve method like so:
resolve(root, args, context, info) {
const topLevelFields = Object.keys(graphqlFields(info));
return fetch(`/api/user?fields=${topLevelFields.join(',')}`);
}
It's best to avoid squeezing it all into one resolver function. Instead, create a separate ObjectType for Cars which has its own fields and its own resolver function. This way, the car query is only called if that field is requested.
In case you are using a RDS, join monster and data louder can help optimize performance of your queries.
Join Monster which relies on generating one big join query and also solve the problem of only requesting exactly the fields you need from the DB
Cached and Batched SQL Data Source which uses facebook's dataloader under the hood - it wont solve the problem of which fields to query (although the example uses knex, which will make that a lot easier), but instead it can cache and batch your queries

Graphql, nodejs, how to resolve non-root level query field based on if it is queried?

I'd like to resolve a field called 'outstandingBalance' in Client type. If front-end query:
query {
Client {
id
name
outstandingBalance
}
}
The resolver function for outstandingBalance is expensive to run. If front-end query:
query {
Client {
id
name
}
}
Then, don't trigger the resolver for 'outstandingBalance'. I have basic understanding of graphql and read most of its official document. But have not seen an answer to this pattern, or this way of using Graphql is not allowed?
Question
Is there a thing called "Nont-root level resolver" for graphql? like the 'outstandingBalance' field in Client type?
Question: How to implement in graphql? Especially using resolver:
async function outstandingBalance(obj, args, context, info) {
console.log('called...')
}
to query one field in one type based on if this field is queried?

Which is the correct resolver function approach?

I would like to clarify which approach I should use for my resolver functions in Apollo + GraphQL
Let's assume the following schema:
type Post {
id: Int
text: String
upVotes: Int
}
type Author{
name: String
posts: [Post]
}
schema {
query: Author
}
The ApoloGraphql tutorial suggests a resolver map like this:
{Query:{
author(_, args) {
return author.findAll()
}
}
},
Author {
posts: (author) => author.getPosts(),
}
As far as I know, every logic regarding posts e.g. get author with posts where the count of post upVotes > args.upVotes, must be handled in the author method. That gives us the following resolver map:
{Query:{
author(_, args) {
return author.findAll({
include:[model: Post]
where: {//post upVotes > args.upVotes}
})
}
},
Author {
posts: (author) => author.getPosts(),
}
Calling author, will first select the author with posts in one joined query, where posts are greater than args.upVotes. Then it will select the posts for that author again, in an additional query because of Author ... getPosts()
Technically, I can reach the same result by removing Author, since posts are already included in the small author method.
I have the following questions:
Do I need this statement? In which cases?
Author {
posts: (author) => author.getPosts(),
}
If no, then how can I find out if the posts field was requested so that
I can make the posts include conditionally, depending not only on the
arguments, but also on the requested fields?
If yes, which posts will contain the final result? Posts from the
include statement, or the getPosts()?
The resolver map you included in your question isn't valid. I'm going to assume you meant something like this for the Author type:
Author {
posts: (author) => author.getPosts(),
}
As long as your author query always resolves to an array of objects that include a posts property, then you're right in thinking it doesn't make sense to include a customer resolver for the posts field on the Author type. In this case, your query's resolver is already populating all the necessary fields, and we don't have to do anything else.
GraphQL utilizes a default resolver that looks for properties on the parent (or root) object passed down to the resolver and uses those if they match the name of the field being resolved. So if GraphQL is resolving the posts field, and there is no resolver for posts, by default it looks at the Author object it's dealing with, and if there is a property on it by the name of posts, it resolves the field to its value.
When we provide a custom resolver, that resolver overrides the default behavior. So if your resolver was, for example:
posts: () => []
then GraphQL would always return an empty set of posts, even if the objects returned by author.findAll() included posts.
So when would you need to include the resolver for posts?
If your author resolver didn't "include" the posts, but the client requested that field. Like you said, the problem is that we're potentially making an unnecessary additional call in some cases, depending on whether your author resolver "includes" the posts or not. You can get around that by doing something like this:
posts: (author) => {
if (author.posts) return author.posts
return author.getPosts()
}
// or more succinctly
posts: author => author.posts ? author.posts : author.getPosts()
This way, we only call getPosts if we actually need to get the posts. Alternatively, you can omit the posts resolver and handle this inside your author resolver. We can look at the forth argument passed to the resolver for information about the request, including which fields were requested. For example, your resolver could look something like this:
author: (root, args, context, info) => {
const include = []
const requestedPosts = info.fieldNodes[0].selectionSet.selections.includes(s => s.name.value === 'posts'
if (requestedPosts) include.push(Post)
return Author.findAll({include})
}
Now your resolver will only include the posts for each author if the client specifically requested it. The AST tree object provided to the resolver is messy to parse, but there are libraries out there (like this one) to help with that.

Resources