Get data from the graphql endpoint of you Shopify store

Yesterday I just needed a price from our Shopify Shop for one of our backend services. To be precise, I needed a way to request a price from Shopify using an article number / SKU.

Since this is a very specific query, I thought that I would use the GraphQL Api from Shopify 😊 In the following I show how to solve such a problem. The full code can be found at the following URL:

https://gist.github.com/m3tam3re/98d9b57f404db3bc1ff8af133e54afd0

What do we need?

First of all, our program has to send a Http Request to the GraphQL endpoint of Shopify. With this request we naturally want to send a specific query in the form of a query. I.e. we need a function that generates our query based on a given article number and processes the result and returns the price to us.

We don’t actually need any additional packages to implement this. All of this can be solved with the standard library of GO. However, since I wanted to simplify the handling of the JSON answer for myself, I decided to use the package github.com/valyala/fastjson . If you look at the code, you quickly understand why 😇

1. Create a function for the HTTP request

First, we create a function that sends a request to the GraphQL endpoint of our Shopify shop and returns the answer or an error. Since I assume that I might need other data besides the current use case, I decided to put the whole thing in a general function.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
func startRequest(body []byte) (http.Response, error) {
	client := http.Client{
		Timeout: time.Second  120,
	}
	req, err := http.NewRequest("POST", endpoint, bytes.NewBuffer(body))
	if err != nil {
		return nil, fmt.Errorf( "error building request: %s", err)
	}
	req.Header.Add("X-Shopify-Access-Token",token)
	req.Header.Add("Content-Type", "application/graphql")

	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf( "error executing request: %s", err)
	}
	return resp, nil
}

The function is actually quite simple:

  • we create an HTTP client with a timeout so that the request is canceled in case it takes too long
  • we create a new POST request, to which we send the URL and the content / body in addition to the type of request. Since this function requires a io.Reader, we convert our body into a buffer that implements the io.Reader interface
  • we add the X-Shopify-Access-Token to the header of our request and define that the request is of the type application/graphql
  • then we execute the request with client.Do (req)
  • on the way we still handle errors that may occur

2. Create a function to query the price

The second function we are going to write is to build our query for the GraphQL endpoint and to send the whole thing to our startRequest() function. Also the returned result should be evaluated and returned to our main function.

I think creating an own type for unmarshaling the JSON answer is overkill, we only want one value! Therefore we use the fastjson package.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
func getItemPrice(sku string) (float32, error) {
	query := `{
  inventoryItems(first:1, query:"sku:` + sku + `") {
    edges{
      node{
        sku
        variant {
          price
        }
      }
    }
  }
}`
	resp, err := startRequest([]byte(query))
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return 0, fmt.Errorf( "error reading response body: %s", err)
	}
	p := fastjson.GetString(body, "data", "inventoryItems", "edges", "0", "node", "variant", "price")
	if fastjson.Exists(body, "error") {
		return 0, fmt.Errorf("shopify responded with an error: %s", fastjson.GetString(body, "error"))
	}
	if fastjson.Exists(body, "data", "inventoryItems", "edges", "0", "node", "variant", "price") == false {
		return 0, fmt.Errorf("item could not be found in shopify: %s", sku)
	}
	price, err:= strconv.ParseFloat(p, 32)
	if err != nil {
		return 0, fmt.Errorf( "error reading response body: %s", err)
	}
	return float32(price), nil
}

I’ll explain from top to bottom what the function does:

  • First we define the query for the GraphQL endpoint. Basically, this is just a composite string in which the article number that we provided to the function is inserted
  • then we pass the query to our startRequest() as a byte slice and receive the response from Shopify as \http.Response
  • With defer or Body.Close() we ensure that the content of the answer is closed after completion. Moreover we use a reader to save the content in the variable body
  • Now we use the fastjson.GetString() function to get the price from Shopify’s response and then generate an error in the line if Shopify reports an error.
  • We also deal with the case that the price field is not returned by Shopify (e.g. if Shopify does not know the item)
  • Finally, we make the string that Shopify provides us with a float and return the price to the main function

How to use fastjson

I quickly went over how exactly we get our price with fastjson. Basically we need to move along the JSON answer: Shopify’s answer looks something like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
  "data": {
    "inventoryItems": {
      "edges": [
        {
          "node": {
            "sku": "mySKU",
            "variant": {
              "price": "144.90"
            }
          }
        }
      ]
    }
  },
  "extensions": {
    "cost": {
      "requestedQueryCost": 4,
      "actualQueryCost": 4,
      "throttleStatus": {
        "maximumAvailable": 1000.0,
        "currentlyAvailable": 996,
        "restoreRate": 50.0
      }
    }
  }
}

Our fastjson.GetString() function call looked like this:

1
fastjson.GetString(body, "data", "inventoryItems", "edges", "0", "node", "variant", "price")

As you can see, we pass Shopify’s complete response through the variable body followed by the structure we find in the JSON response until we end up with price. The only thing you really have to consider here is that the edges in the answer are a list. Therefore we have to tell fastjson which element of the list we want. We only have one element here, so we give the index 0: … “edges”, “0”, “node” …

With fastjson.Exists() you can check whether a certain value is contained in the answer and react accordingly. Of course, fastjson has other useful functions. More on this at:

https://github.com/valyala/fastjson

3. The main() function

The main function is relatively simple. We pass getItemPrice() the article number we are looking for, check whether there was an error and otherwise output the price:

1
2
3
4
5
6
7
func main() {
	price, err := getItemPrice("your_SKU") // The SKU you are looking for
	if err != nil {
		log.Fatalf("could not get price from shopify: %s", err)
	}
	fmt.Println("Price:", price)
}

As I said, you can find the complete code at:

https://gist.github.com/m3tam3re/98d9b57f404db3bc1ff8af133e54afd0

4. That’s it 😇

As you can see, it is relatively easy to get data from your Shopify shop. In addition to a few programming skills, it is of course helpful if you are familiar with GraphQL.

One could legitimately criticize this small program. The function getItemPrice() has too many responsibilities. In a larger context, the query creation would probably have been outsourced to its own function. I think the principle is still clear.

0%