Have your say in Community Polls: What was/is your greatest motivation to start your own business?
Our Partner & Developer boards on the community are moving to a brand new home: the .dev community forums! While you can still access past discussions here, for all your future app and storefront building questions, head over to the new forums.

Re: GraphQL Blog/Article

GraphQL Blog/Article

wheey
Tourist
4 0 1

Hi Shopify community,

I'm trying to build my website using React and GraphQL and I have stuck on blog articles, what I have is all published articles for one blog which shows in boxes, and that's fine, they are ordered as they are published.

Every published article for one blog should show previous (if exist) and next (if exist) article, and since Shopify GraphQL does not offer that out of the box, I have implemented this by sending two queries defined as follows:

 

 

query nextArticle($blogCategoryHandle: String!, $query: String!) {
    blogByHandle(handle: $blogCategoryHandle) {
        id
        handle
        articles(query: $query, sortKey: PUBLISHED_AT, first: 5) {
            edges {
                node {
                    id
                    handle
                    publishedAt
                }
            }
        }
    }
}

query prevArticle($blogCategoryHandle: String!, $query: String!) {
    blogByHandle(handle: $blogCategoryHandle) {
        id
        handle
        articles(query: $query, reverse: true, sortKey: PUBLISHED_AT, first: 5) {
            edges {
                node {
                    id
                    handle
                    publishedAt
                }
            }
        }
    }
}

 

 

 

Requests and responses:

Next:

 

 

operationName	"nextArticle"
query	"query nextArticle($blogCategoryHandle: String!, $query: String!) {\n blogByHandle(handle: $blogCategoryHandle) {\n id\n handle\n articles(query: $query, sortKey: PUBLISHED_AT, first: 5) {\n edges {\n node {\n id\n handle\n publishedAt\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n}\n"
variables	{…}
blogCategoryHandle	"zdravlje-i-ljepota"
query	"created_at:>'2021-01-25T14:30:00Z'"

 

 

Previous:

 

 

operationName	"prevArticle"
query	"query prevArticle($blogCategoryHandle: String!, $query: String!) {\n blogByHandle(handle: $blogCategoryHandle) {\n id\n handle\n articles(query: $query, reverse: true, sortKey: PUBLISHED_AT, first: 5) {\n edges {\n node {\n id\n handle\n publishedAt\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n}\n"
variables	{…}
blogCategoryHandle	"zdravlje-i-ljepota"
query	"created_at:<'2021-01-25T14:30:00Z'"

 

 

 

In above,

First problem:

I would expect that NEXT return array of 5 articles which are newer than given DateTime in query which actually does but it also returns same one - this one is easy to fix with array filtering and taking first index (-fine by me). The same goes for PREVIOUS but I'm returning array in reverse order - and it has the same problem as NEXT.

Please note that I have tried adding/substracting some minutes up to 1 day to query datetime but in return some articles are skipped which are few days after or before..

Also, please note that I can live with that but it's annoying, it's still a bug(?).

Second problem (huge):

When I get to one before last, NEXT one is empty which is not true - there is last one, and PREVIOUS one is always same as current open article on both one before last and last.

 

Latest question, since query does have created_at param and sort offers only publishedAt and in return we have publishedAt, what one has to do with other created_at != publishedAt, how it that one considered is created_at and publishedAt same thing here?

 

Thank you all in advance!

Replies 5 (5)

martinfotp
Shopify Partner
24 4 17

You could try using the `before` and `after` with sortKey: `PUBLISHED_AT`:

 

query RELATED($cursor: String) {
  before: articles(before: $cursor, last: 5, sortKey: PUBLISHED_AT) {
    edges {
      cursor
      node {
        ...article
      }
    }
  }
  after: articles(after: $cursor, first: 5, sortKey: PUBLISHED_AT) {
    edges {
      cursor
      node {
        ...article
      }
    }
  }
}
fragment article on Article {
  id
  handle
  publishedAt
}

 

(note how I am using GraphQL alias to do the before and after lookup in a single query!)

Result:

{
  "data": {
    "before": {
      "edges": [
        {
          "cursor": "eyJsYXN0X2lkIjo1NTU0NzQ5NzY4NDYsImxhc3RfdmFsdWUiOjU1NTQ3NDk3Njg0Nn0=",
          "node": {
            "id": "Z2lkOi8vc2hvcGlmeS9BcnRpY2xlLzU1NTQ3NDk3Njg0Ng==",
            "handle": "article-1",
            "publishedAt": "2021-03-13T15:01:59Z"
          }
        }
      ]
    },
    "after": {
      "edges": [
        {
          "cursor": "eyJsYXN0X2lkIjo1NTU0NzUxNzM0NTQsImxhc3RfdmFsdWUiOjU1NTQ3NTE3MzQ1NH0=",
          "node": {
            "id": "Z2lkOi8vc2hvcGlmeS9BcnRpY2xlLzU1NTQ3NTE3MzQ1NA==",
            "handle": "article-3",
            "publishedAt": "2021-03-13T15:16:29Z"
          }
        }
      ]
    }
  }
}

 

Now the annoying thing is, `before` and `after` accept a cursor, not a Node ID. So if you previously fetched the article using articleByHandle, you won't have a cursor.

Good news is, we can actually create the cursor.

Take the id and cursor for `article-1` in my above query as an example. These are base64 encoded values.

The Article Id: `Z2lkOi8vc2hvcGlmeS9BcnRpY2xlLzU1NTQ3NDk3Njg0Ng==` decoded with `atob()` is `gid://shopify/Article/555474976846`

The cursor: `eyJsYXN0X2lkIjo1NTU0NzQ5NzY4NDYsImxhc3RfdmFsdWUiOjU1NTQ3NDk3Njg0Nn0=` decoded with `atob()` is `{"last_id":555474976846,"last_value":555474976846}`. 

Note how the integer ID is the same? So you could create the cursor ID with something like the following:

function getCursorForArticle(articleId) {
  const id = parseInt(atob(articleId).split("/").pop());
  return btoa(JSON.stringify({ last_id: id, last_value: id }));
}

// ---

> getCursorForArticle("Z2lkOi8vc2hvcGlmeS9BcnRpY2xlLzU1NTQ3NDk3Njg0Ng==")
"eyJsYXN0X2lkIjo1NTU0NzQ5NzY4NDYsImxhc3RfdmFsdWUiOjU1NTQ3NDk3Njg0Nn0="

 

Once you have the cursor, you just need to pass that into the RELATED query! Hope that helps!

wheey
Tourist
4 0 1

Hi @martinfotp, this is very good example of how to properly use GraphQL to get everything in one request and I appreciate it!

Tho, I did implement your way, but still problem persists, not just that persists, but in previous result I have nothing, and the next one is always the same results 🙂 I was looking at your query and everything seems in order and there should be some results even wrong, but nothing? So, I think it's a bug in shopify graphql - could be actually .. without their support I'm quite nowhere 🙂

If you know some other way how to debug the problem I would appreciate it very much.

Thank you.

martinfotp
Shopify Partner
24 4 17

@wheey wrote:

Hi @martinfotp, this is very good example of how to properly use GraphQL to get everything in one request and I appreciate it!

Tho, I did implement your way, but still problem persists, not just that persists, but in previous result I have nothing, and the next one is always the same results 🙂 I was looking at your query and everything seems in order and there should be some results even wrong, but nothing? So, I think it's a bug in shopify graphql - could be actually .. without their support I'm quite nowhere 🙂

If you know some other way how to debug the problem I would appreciate it very much.

Thank you.


I've added a few more articles to my test store and it's working as expected. Are you seeing this even in GraphiQL / GraphQL playground? If you're using Apollo, there could be some additional caching issues.

wheey
Tourist
4 0 1

Hello @martinfotp, thank you so much on your prompt responses, I have done additional checks and I'm not sure why the behavior is like that, it really should work but it doesn't 🙂 I'm using Apollo and I have disabled cache policy but without any changes, even without it, I can see POST request to API endpoint and I have removed possible cache issue from list.

So let me try to give you better view on that with my request, this request is for second from last article, and there should be response in previous for one edge node.

operationName	"Related"
query	"query Related($cursor: String!) {\n previous: articles(before: $cursor, sortKey: PUBLISHED_AT, last: 5) {\n edges {\n node {\n id\n handle\n publishedAt\n __typename\n }\n __typename\n }\n __typename\n }\n next: articles(after: $cursor, sortKey: PUBLISHED_AT, first: 5) {\n edges {\n node {\n id\n handle\n publishedAt\n __typename\n }\n __typename\n }\n __typename\n }\n}\n"
variables	{…}
cursor	"eyJsYXN0X2lkIjo0OTI2NzUyMzYwMjEsImxhc3RfdmFsdWUiOjQ5MjY3NTIzNjAyMX0="

Looking at cursor from above this is generated from ID Z2lkOi8vc2hvcGlmeS9BcnRpY2xlLzQ5MjY3NTIzNjAyMQ== (Decoded: gid://shopify/Article/492675236021) of current article which  I got from requesting blog by handle and article by handle inside (I want to have both, blog title and article title for breadcrumb purpose), doing decoding of cursor I will get {"last_id":492675236021,"last_value":492675236021}, which is fine.

Now, when I decode IDs from bellow responses, for example, just taking first one (which should be only previous article in current article), I will get GID as gid://shopify/Article/492648988853, you can notice that current article, actually cursor ID is bigger number than first from next response. If I take third one (second one is same: gid://shopify/Article/492675236021) I'll get gid://shopify/Article/492690866357, and by that, this should work flawless, but it's just wrong response.

{
	"data": {
		"previous": {
			"edges": [],
			"__typename": "ArticleConnection"
		},
		"next": {
			"edges": [
				{
					"node": {
						"id": "Z2lkOi8vc2hvcGlmeS9BcnRpY2xlLzQ5MjY0ODk4ODg1Mw==",
						"handle": "ulje-industrijske-konoplje-koliko-vazno-kozu-organizam",
						"publishedAt": "2021-01-20T13:30:00Z",
						"__typename": "Article"
					},
					"__typename": "ArticleEdge"
				},
				{
					"node": {
						"id": "Z2lkOi8vc2hvcGlmeS9BcnRpY2xlLzQ5MjY3NTIzNjAyMQ==",
						"handle": "blagodati-maslinovog-ulja-za-vasu-kozu",
						"publishedAt": "2021-01-25T13:30:00Z",
						"__typename": "Article"
					},
					"__typename": "ArticleEdge"
				},
				{
					"node": {
						"id": "Z2lkOi8vc2hvcGlmeS9BcnRpY2xlLzQ5MjY5MDg2NjM1Nw==",
						"handle": "koje-su-blagodati-upotrebe-ulja-avokada-na-vasoj-kozi",
						"publishedAt": "2021-01-31T13:30:00Z",
						"__typename": "Article"
					},
					"__typename": "ArticleEdge"
				},
				{
					"node": {
						"id": "Z2lkOi8vc2hvcGlmeS9BcnRpY2xlLzQ5MjY5MjE3NzA3Nw==",
						"handle": "neem-drvo-zdravlja-u-prirodnoj-kozmetici",
						"publishedAt": "2021-02-06T13:30:00Z",
						"__typename": "Article"
					},
					"__typename": "ArticleEdge"
				},
				{
					"node": {
						"id": "Z2lkOi8vc2hvcGlmeS9BcnRpY2xlLzQ5Mjc0MTk1MTY2OQ==",
						"handle": "koristi-ulja-nocurka-i-nacini-koristenja",
						"publishedAt": "2021-02-11T13:30:00Z",
						"__typename": "Article"
					},
					"__typename": "ArticleEdge"
				}
			],
			"__typename": "ArticleConnection"
		}
	}
}

 

I'm not sure, am I blind not seeing error in my query or it's just bad luck here.

One question tho, which API version are you using?

 

Thank you!

wheey
Tourist
4 0 1

Hi @martinfotp, I'm still testing it, but I think I figured this out, for anyone who will run on same issue what I did is replaced first: 5 and last:5 with first:1 and last:1, removed sortKey and left cursor positioning, which in return will always go 1 after and 1 before.

Thank you very much for shining a light into this darkness 🙂