REST to GRAPHQL

Started by Ron Klassen, January 08, 2025, 04:07:06 PM

Previous topic - Next topic

Ron Klassen

I have to convert a REST based API to GRAPHQL and I was wondering if anyone has any experience with this?
Does anyone have any experience working with GRAPHQL, ideally in a SHOPIFY environment?

Loren Doornek

I'm doing the same thing, with PXPlus and Shopify.  I've been using some of the GraphQL functionality for a couple of years since some of the functionality I needed was only available via GraphQL.  Now that Shopify is moving everything to GraphQL, I'm gradually converting the calls, so I'm fairly familiar with it.  REST is certainly easier to program with PXPlus using the JSON functionality in the DIM () function.  I haven't figured out a simple way to do the GraphQL calls other than just hard-coding all of the field names and such that you need in the response.  Once you get the data back, the structure is essentially the same as the REST data, except it only contains the fields that you requested rather than the entire record.  So, I'm finding myself digging through all of my code trying to find every field name that I always relied on to be there automatically, since now I need to specifically request all of those field names (after confirming that the field name isn't changed in GraphQL, which happens far too often!)  Feel free to ask any questions you might have, and I'll see if I can answer them.  Could be good info for a lot of people that might need to work with GraphQL in the future, since that seems to be becoming more prominent in various web interfaces.

Ron Klassen

Thanks Loren, I appreciate any assistance you can provide. I'm going to convert a few simple REST functions to GraphQL and see how it goes.
Do you still use the same *plus/web/request endpoints?

Ron Klassen

I tried to set up a simple query to get the name and address of a customer, and I am getting nowhere. I can't figure out how to define the GraphQL query. Do you have an example GraphQL query you could share?

Loren Doornek

I don't use *plus/web/request, since we built SOAP interfaces before PXP came to be, and we've just adapted them to REST.  But, you should be able to use the examples below with the POST data to pass to *plus/web/request.

The endpoint should always be the graphql.json endpoint at https://<STORE_NAME>.myshopify.com/admin/api/<API_VERSION>/graphql.json . The endpoint isn't different for each item you need to retrieve (like customers, or orders, or products) like it is with REST.  You also generally need to refer to every item by its' GID rather than its' ID.

The biggest difference for me is the need to manually build the queries, since I haven't found a simple way to build them, like the DIM() commands can be used to build JSON.  There's a lot of manual "pick and shovel" work (as we old-timers call it!) just typing in the queries and making sure that you don't miss a curly brace somewhere!

Below is an example program of retrieving a customer, using the known GID for the customer.  Note that the admin_api_path$ must contain your URL (ie: https://<STORE_NAME>.myshopify.com/admin/api/<API_VERSION>/).

0010 LET gid$="gid://shopify/Customer/5432101234"
0020 LET query$="query { customer(id: """+gid$+""") { id email numberOfOrders createdAt defaultAddress { id name address1 address2 city provinceCode zip countryCode} }}"
0030 LET qry$["query"]=query$; LET post$=DIM(LIST qry${ALL})
0040 LET type$="POST",url$=admin_api_path$+"/graphql.json"

And here is the full request that is sent to Shopify:
POST https://my_dev_store.myshopify.com/admin/api/2024-07/graphql.json HTTP/1.1
Host: my_dev_store.myshopify.com
Authorization: Basic xMGY4MzYxOThkNzdiZWQyNTljZmI6NGRiZWZkZTUzMzNmOWRmYjgwZjJOWVjMWYxZDAzMDYhYzhkY2NkNWIxZTY=
Connection: Close
Cache-Control: no-cache
Content-Type: application/json; charset=utf-8
Content-Length: 188

{"query":"query { customer(id: \"gid://shopify/Customer/5432101234\") { id email numberOfOrders createdAt defaultAddress { id name address1 address2 city provinceCode zip countryCode}}}"}

That should return the customer data requested.  The JSON is basically the same as REST, but there is also a "data." structure as the base structure in the JSON.  An example response to the above request is shown below.

{
  "data":{
    "customer":{
      "id":"gid://shopify/Customer/55432101234",
      "email":"test@sample.com",
      "numberOfOrders":"60",
      "createdAt":"2017-04-20T19:10:27Z",
      "defaultAddress":{
        "id":"gid://shopify/MailingAddress/685920161868?model_name=CustomerAddress",
        "name":"Test User",
        "address1":"23456 Oak Hill Drive",
        "address2":"",
        "city":"Laguna Hills",
        "provinceCode":"CA",
        "zip":"92653",
        "countryCode":"US"
      }
    }
  },
  "extensions":{
    "cost":{
      "requestedQueryCost":1,
      "actualQueryCost":1,
      "throttleStatus":{
        "maximumAvailable":2000.0,
        "currentlyAvailable":1999,
        "restoreRate":100.0
      }
    }
  }
}


If you don't know any of your customer GID's, here's a quick query to return the first 10:

0020 LET query$="query { customers(first: 10) {edges {node {id}}}}"
0030 LET qry$["query"]=query$; LET post$=DIM(LIST qry${ALL})
0040 LET type$="POST",url$=admin_api_path$+"/graphql.json"

The full post data is shown below:
{"query":"query { customers(first: 10) {edges {node {id}}}}"}

Hope that's enough info to get you started! 



Ron Klassen

Thanks Loren, this will certainly help. I've got a couple of simple requests working, and now I'm working on a much more complicated request.

Ron Klassen

I'm having a lot of trouble with the 'quantities' field in the InventoryItem section to work. For example, the following works fine:

{ "query": "query { inventoryItem(id:\"gid://shopify/InventoryItem/41450849599575\") { id inventoryLevels(first:2) { edges { node { id location { id name } } } } } }" }

However, if I insert the quantities logic:

{ "query": "query { inventoryItem(id:\"gid://shopify/InventoryItem/41450849599575\") { id inventoryLevels(first:2) { edges { node { id location { id name } quantities(names: ["available"]) { name quantity } } } } } }" }

The response from the query is 'Bad Request'.

I've taken this logic from numerous examples I've found, and I've no clue as to why it doesn't work.

Any ideas would be welcome.


Loren Doornek

you need to 'escape' the quotes around "available" by putting a backslash in front of them.  Try the example below.  I added the \ before the quotes, and also added a second inventory quantity so that you can see how the additional quantities can be requested.

{"query":"query { inventoryItem ( id: \"gid://shopify/InventoryItem/41450849599575\") { id inventoryLevels(first:2) { edges { node { id location { id name } quantities(names: [\"available\",\"on_hand\"]) { name quantity } } } } } }" }


Ron Klassen

Thanks Loren, that was the key. So, do I have to 'escape' quotes whenever I use them?
Strange that none of the examples I've seen had the quotes 'escaped'.

Loren Doornek

Yes, you always need to escape the quotes if they are internal to the query.  (Your original post on the query has escaped the quotes before and after the GID.)

The PXPlus JSON parser will generally handle the escaping for you, if you just build the query with the quotes in it.  Below is an example of the code I used.  Note that I did NOT escape the quotes, but they are there in the JSON because I created the JSON using the DIM(LIST ) functionality of PXPlus, and it properly escapes any necessary characters.

Also note that, when building the query string, I use two quotes together quite a bit (see line 20).  That just tells PVXPlus to include the quote in the string you are building, rather than treating the quote as the end of the string.  A lot of people don't know that trick, so I'm pointing it out just in case you have trouble deciphering line 20 of the code below.

0010 LET gid$="gid://shopify/InventoryItem/33749329936442"
0020 LET query$="query { inventoryItem (id: """+gid$+""") { id inventoryLevels(first:2) { edges { node { id location { id name } quantities(names: [""available"",""on_hand""]) { name quan
0020:tity } } } } } }"
0030 LET qry$["query"]=query$; LET post$=DIM(LIST qry${ALL})
0040 PRINT post$

-;run

{"query":"query { inventoryItem (id: \"gid://shopify/InventoryItem/33749329936442\") { id inventoryLevels(first:2) { edges { node { id location { id name } quantities(names: [\"available\"
,\"on_hand\"]) { name quantity } } } } } }"}
-;

Ron Klassen

You've been a big help Loren. I appreciate all the tips and insights.

Ron Klassen

Now I'm having trouble with a mutation that includes variables. This is the request:

{ "query": "mutation productVariantsBulkUpdate($variants: [ProductVariantsBulkInput!]!, $productId: ID!) { productVariantsBulkUpdate(variants: $variants, productId: $productId) { product { id } productVariants { id barcode } userErrors { code field message } } } { variants: [ { id: \"gid://shopify/ProductVariant/31174618611799\", \"barcode\": \"190496879522\" } ], \"productId\": \"gid://shopify/Product/4353068859479\" }" }

and this is what is received:

{"errors":[{"message":"syntax error, unexpected LBRACKET (\"[\") at [1, 264]","locations":[{"line":1,"column":264}]}]}

I've tried setting up the query by including the variables in the actual query, but got nowhere as well. It looks like pos'n 264 is the opening curly bracket for the variables section Is there a special way of adding the variables section?

Loren Doornek

#12
I always have trouble passing the variables as a separate section, so I just include them in the argument. This also gets rid of the need to define the variables and name the mutation, which lets you get rid of a big chunk of the text in the string.  The text "productVariantsBulkUpdate($variants: [ProductVariantsBulkInput!]!, $productId: "ID!)" really is just naming the function and defining the variables, but that isn't really necessary. 

Here's some code I used to build this mutation.  It builds a JSON string for multiple variants/barcodes to update, then removes the quotes around the field names in the JSON string since GraphQL doesn't use quotes around the field names, and then includes that JSON string as an argument to the function.  The resulting POST$ variable should work for you.

<Posted Code in the next message, since this message is causing it to be formatted incorrectly)

Loren Doornek

0010 BEGIN
0020 LET gid$="gid://shopify/Product/4353068859479"
0030 LET j$["variants.1.id"]="gid://shopify/ProductVariant/31174618611799",j$["variants.1.barcode"]="190496879522"
0040 ! LET j$["variants.2.id"]="gid://shopify/ProductVariant/31174618611799",j$["variants.2.barcode"]="190496879522" ! Additional variants to update
0050 LET v$=DIM(LIST j${ALL})
0060 DIM r$[1:3]; READ DATA FROM "variants,id,barcode,",SEP="," TO r${ALL}; FOR i=1 TO 3; LET v$=SUB(v$,QUO+r$[i]+QUO+":",r$[i]+":"); NEXT i; LET v$=MID(v$,2,LEN(v$)-2) ! Remove the quotes from around the field names since GQL doesn't use them like JSON does
0070 LET query$="mutation { productVariantsBulkUpdate(productId: """+gid$+""", "+v$+") { product { id } productVariants { id barcode } userErrors { field message } } }"
0080 LET qry$["query"]=query$; LET post$=DIM(LIST qry${ALL}); DIM qry$
0090 PRINT post$

Loren Doornek

And here is the resulting query string:

{"query":"mutation { productVariantsBulkUpdate(productId: \"gid://shopify/Product/4353068859479\", variants:[{id:\"gid://shopify/ProductVariant/31174618611799\",barcode:\"190496879522\"}]) { product { id } productVariants { id barcode } userErrors { field message } } }"}