ShopifySharp icon indicating copy to clipboard operation
ShopifySharp copied to clipboard

Generate fluent GraphQL query builders by parsing Shopify's schema

Open nozzlegear opened this issue 1 month ago • 3 comments

Once merged, this PR will add a new GraphQueryBuilder<T> base class, along with the generated query builders of all Shopify GraphQL types, union types, queries and mutations.

This will resolve #1137 and #1132.

nozzlegear avatar Nov 05 '25 18:11 nozzlegear

Hi @nozzlegear, Sounds like you have been working hard on this. I'm curious what it will look like in the end from the user's perspective.

clement911 avatar Dec 01 '25 23:12 clement911

Hey @clement911, hope you're doing well!

My goal with this is to add a strongly-typed fluent API for all of the generated GraphQL types, queries and mutations. Each class will have methods like AddFieldFoo, AddUnionBar and AddArgumentBat, and then we can call .Build() on them to convert them to a GraphQL query string which can be passed to the GraphService.

Here's what it looks like in practice:

var shopQueryBuilder = new ShopQueryBuilder();
var graphQuery = shopQueryBuilder.AddFieldName()
  .AddFieldContactEmail()
  .AddFieldCurrencyCode()
  .AddFieldUrl()
  .Build();

Which will output this GraphQL string:

shop {
  name
  contactEmail
  currencyCode
  url
}

A more complicated example with arguments, unions and nested objects might look something like this:

var ordersQuery = new OrdersQueryBuilder();
ordersQuery.AddArgumentFirst(50)
  .AddFieldPageInfo(i =>
    {
      i.AddFieldStartCursor()
        .AddFieldEndCursor()
        .AddFieldHasPreviousPage()
        .AddFieldHasNextPage();
      return i;
    })
  .AddFieldNodes(n =>
    {
      n.AddFieldId()
        .AddFieldName()
        .AddUnionPurchasingEntity((PurchasingCompany u) =>
        {
          u.AddFieldCompany(c =>
          {
            c.AddFieldName();
            return c;
          });
          return u;
        })
        .AddUnionPurchasingEntity((Customer c) =>
        {
          c.AddFieldFirstName()
            .AddFieldLastName();
          return c;
        })
      return n;
    })
  .Build();

Which should create this GraphQL string:

orders (first: 50) {
  pageInfo {
    startCursor
    endCursor
    hasPreviousPage
    hasNextPage
  }
  nodes {
    id
    name
    ... on PurchasingCompany {
      company {
        name
      }
    }
    ... on Customer {
      firstName
      lastName
    }
  }
}

I'm planning on adding a special case to the GraphService that will let us pass these querybuilders directly to the service, where it will handle the serialization of the graphql string/parameterization of arguments, etc.

I'm also not sold on the names of the AddFieldFoo, AddUnionBar, AddArgumentBaz methods yet, so they're still open to change and I'm open to suggestions there. I was considering something like builder.Fields.AddFoo(), builder.Arguments.AddBar() and builder.Unions.AddBaz() instead.

Let me know what you think!

nozzlegear avatar Dec 03 '25 06:12 nozzlegear

Oh I see. That's quite ambitious. I see how it will be useful to build queries dynamically in a type safe way 👍

clement911 avatar Dec 03 '25 23:12 clement911