Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GraphQL queries with tables join using Node.js

I am learning GraphQL so I built a little project. Let's say I have 2 models, User and Comment.

const Comment = Model.define('Comment', {
  content: {
    type: DataType.TEXT,
    allowNull: false,
    validate: {
      notEmpty: true,
    },
  },
});

const User = Model.define('User', {
  name: {
    type: DataType.STRING,
    allowNull: false,
    validate: {
      notEmpty: true,
    },
  },
  phone: DataType.STRING,
  picture: DataType.STRING,
});

The relations are one-to-many, where a user can have many comments.
I have built the schema like this:

const UserType = new GraphQLObjectType({
  name: 'User',
  fields: () => ({
    id: {
      type: GraphQLString
    },
    name: {
      type: GraphQLString
    },
    phone: {
      type: GraphQLString
    },
    comments: {
      type: new GraphQLList(CommentType),
      resolve: user => user.getComments()
    }
  })
});

And the query:

const user = {
  type: UserType,
  args: {
    id: {
      type: new GraphQLNonNull(GraphQLString)
    }
  },
  resolve(_, {id}) => User.findById(id)
};

Executing the query for a user and his comments is done with 1 request, like so:

{
  User(id:"1"){
    Comments{
      content
    }
  }
}

As I understand, the client will get the results using 1 query, this is the benefit using GraphQL. But the server will execute 2 queries, one for the user and another one for his comments.

My question is, what are the best practices for building the GraphQL schema and types and combining join between tables, so that the server could also execute the query with 1 request?

like image 455
itaied Avatar asked Dec 03 '16 08:12

itaied


People also ask

Can GraphQL do joins?

The data coming in from a GraphQL API can be joined with data already existing in a database. This is possible by creating a relationship from a field in the custom GraphQL server to a field in the database table.

How do I join two tables in node JS?

Join Two or More Tables You can combine rows from two or more tables, based on a related column between them, by using a JOIN statement.

What is QL graph android?

GraphQL is a query language for the APIs for getting your data. It is an alternative for the REST APIs. It is not specific for a single platform and works for all type of clients including Android, iOS or web. It stands between your server and client and helps you to query your data in a more optimized way.

What is Apollo node JS?

Apollo Server is a community-maintained open-source GraphQL server. It works with many Node. js HTTP server frameworks, or can run on its own with a built-in Express server. Apollo Server works with any GraphQL schema built with GraphQL. js--or define a schema's type definitions using schema definition language (SDL).


2 Answers

The concept you are refering to is called batching. There are several libraries out there that offer this. For example:

  • Dataloader: generic utility maintained by Facebook that provides "a consistent API over various backends and reduce requests to those backends via batching and caching"

  • join-monster: "A GraphQL-to-SQL query execution layer for batch data fetching."

like image 100
marktani Avatar answered Sep 17 '22 16:09

marktani


To anyone using .NET and the GraphQL for .NET package, I have made an extension method that converts the GraphQL Query into Entity Framework Includes.

public static class ResolveFieldContextExtensions
{
    public static string GetIncludeString(this ResolveFieldContext<object> source)
    {
        return string.Join(',', GetIncludePaths(source.FieldAst));
    }

    private static IEnumerable<Field> GetChildren(IHaveSelectionSet root)
    {
        return root.SelectionSet.Selections.Cast<Field>()
                                           .Where(x => x.SelectionSet.Selections.Any());
    }

    private static IEnumerable<string> GetIncludePaths(IHaveSelectionSet root)
    {
        var q = new Queue<Tuple<string, Field>>();
        foreach (var child in GetChildren(root))
            q.Enqueue(new Tuple<string, Field>(child.Name.ToPascalCase(), child));

        while (q.Any())
        {
            var node = q.Dequeue();
            var children = GetChildren(node.Item2).ToList();
            if (children.Any())
            {
                foreach (var child in children)
                    q.Enqueue(new Tuple<string, Field>
                                  (node.Item1 + "." + child.Name.ToPascalCase(), child));

            }
            else
            {
                yield return node.Item1;
            }
        }}}

Lets say we have the following query:

query {
  getHistory {
    id
    product {
      id
      category {
        id
        subCategory {
          id
        }
        subAnything {
          id
        }
      }
    }
  }
}

We can create a variable in "resolve" method of the field:

var include = context.GetIncludeString();

which generates the following string:

"Product.Category.SubCategory,Product.Category.SubAnything"

and pass it to Entity Framework:

public Task<TEntity> Get(TKey id, string include)
{
    var query = Context.Set<TEntity>();
    if (!string.IsNullOrEmpty(include))
    {
        query = include.Split(',', StringSplitOptions.RemoveEmptyEntries)
                       .Aggregate(query, (q, p) => q.Include(p));
    }
    return query.SingleOrDefaultAsync(c => c.Id.Equals(id));
}
like image 25
ceferrari Avatar answered Sep 19 '22 16:09

ceferrari