Back to Blog Home
← all posts

Using the Shopify Admin API in Nativescript with Vue

January 21, 2021 — by Oscar Lira

Shopify is an e-commerce platform that allows you to easily and quickly create, configure and run your own online store. In accordance with Shopify, more than 1,000,000 merchants use Shopify around the world.

Shopify provides API endpoints to interact with data about individual Shopify stores, data such as products, orders, customers and more.

Let's see how we can implement CRUD functionality with products in a Nativescript Vue app using the Shopify Admin API.

Give Oscar Lira a follow!

Shopify Private Apps

Private apps are built exclusively for individual Shopify stores. You can use them to access data directly using Shopify's APIs or extend online stores to other platforms; in our case mobile apps.

So we need to go to the Shopify store and create a private app in order to generate the Api Key we're gonna use in our Nativescript app and provide Read and Write permissions for the products as well. (if you don't know how to create a private app you can visit this link)

img

Let's Create Our App

Once we have the Api Key and the permissions for the products in Shopify, we create the Nativescript project with Vue, in this case I use Typescript, but if you want to use plain JavaScript, no problem!! the scaffolding of this app would be the same

vue init nativescript-vue/vue-cli-template shopify-nativescript
npm install

Remember to have Nativescript up to date to use the latest version of it

Now go into the app folder and add the followin packages:

"@nstudio/nativescript-cardview": "^2.0.1"
"@nstudio/nativescript-floatingactionbutton": "^3.0.4"
"nativescript-toast": "^2.0.0"
"vue-class-component": "^7.2.6",
"vue-property-decorator": "^9.1.2"

Defining the Shopify Service class

First we need the URL of the Shopify store and the base64 String formed by the Api Key and Password.

Follow the next two steps to generate it

  1. Join the API key and password with a single colon (:)

  2. Encode the resulting string in base64 representation

    You can get more info here

Then create a ShopifyService class where will contain all the requests to the Shopify Admin API

import { Http } from "@nativescript/core";

export default class ShopifyService {
    static STOREURL:string = "https://nativescript-store-1.myshopify.com/"
    static BASIC_AUTHENTICATION:string = "YOUR_BASE64"
}

Listing Shopify Products in the app

In the ShopifyService Class add the viewProduct function to retrieve the products of the store...

🧐 Notice we're requesting the endpoint admin/api/2021-01/products.json and adding the basic Authentication (base64) in the header of the request as well.

Here is the complete list of Shopify endpoints https://shopify.dev/docs/admin-api/rest/reference

static ViewProducts() {
    return Http.request({
        url: this.STOREURL + 'admin/api/2021-01/products.json',
        method: 'GET',
        headers: {
            'Authorization': 'Basic ' + this.BASIC_AUTHENTICATION,
            "Content-Type": "application/json"
        },
    });
}

Our main App.vue view is going to have a ListView and a Floating Action Button to add products:

<Page>
    <ActionBar title="Shopify API in NativeScript-Vue!" />
    <GridLayout columns="*" rows="*">
      <ActivityIndicator row="0" :busy="loading" 
       :visibility="!loading ? 'collapse' : 'visible'" verticalAlignment="top"/>
      <ListView for="item in products" row="0">
        <v-template>
          <CardProduct :p_product="item" />
        </v-template>
      </ListView>
      <Fab @tap="add" row="0" rippleColor="#f1f1f1" class="fab-button fa">
          {{"fa-plus" | fonticon}}
      </Fab>
    </GridLayout>
  </Page>

and it looks like this 😎😎😎

The code for the App.vue contains the mounted hook where will dispatch the load action to call the Shopify products via Vuex

I use Vuex Modules and I have a separate file for actions, getters, mutation-types, mutation and modules

😬 No worries! at the end of the post I share the complete code in Git Hub

Remember that the mounted function executes when the view is loaded

<script lang="ts">
...
export default class App extends Vue {
      mounted() {
        let vm = this;
        vm.loading = true;
        this.$store.dispatch("shopifyProducts/load").then(function () {
          vm.loading = false;
        });
  }
...
}

This is the load action in Vuex, here it calls the viewProducts function, previously created in the ShopifyService class and adds each product in an array via a mutation

export const load = ({ commit }) => {
    return new Promise((resolve, reject) => {
        ShopifyService.ViewProducts().then(function (data) {
            let content = JSON.parse(data.content+"");
            content.products.forEach(p => {
                commit(types.ADD, {
                    id: p.id,
                    title: p.title,
                    body_html: p.body_html == null ? '':p.body_html
                })
            });
            resolve(true)
        });
    });
}

🧐 Notice we have a CardProduct.vue component in the main App.vue inside the ListView element

<card-view margin="5" elevation="10" radius="1">
    <StackLayout>
      <label :text="p_product.title" class="h2"></label>
      <Image :src="imgSrc" stretch="aspectFit" />
      <HtmlView :html="p_product.body_html" />
      <StackLayout class="m-10 hr"></StackLayout>
      <StackLayout orientation="horizontal">
        <Label @tap="delete_" horizontalAlignment="right" class="text-danger h3 fa"
  		style="margin-left: 0">{{ "fa-trash-o" | fonticon }} Delete</Label>
        <Label @tap="edit" horizontalAlignment="right" class="text-primary h3 fa">
            {{ "fa-pencil-square-o" | fonticon }} Edit</Label>
      </StackLayout>
    </StackLayout>
</card-view>

This component will show each product in the ListView receiving a product as a prop to show the title and description of the product, but what about the image?

image-20210117150508007

... Its corresponding code will dispatch the loadImageByProduct action via Vuex to get the images of the products inside the mounted hook

...
@Prop() private p_product: {
    id: "";
    title: "";
    body_html: "";
  };
...
mounted() {
    let vm = this;
    store.dispatch("shopifyProducts/loadImageByProduct", this.p_product)
    .then(function (img) {
        vm.imgSrc = img;
    })
}
...

In the ShopifyService class we have the function which gets the images of the product with the URL admin/api/2021-01/products/${product_id}/images.json. This function receives the product_id argument to identify the product in Shopify

static loadImageByProduct(product_id) {
        return Http.request({
            url: this.STOREURL + `admin/api/2021-01/products/${product_id}/images.json`,
            method: 'GET',
            headers: {
                'Authorization': 'Basic ' + this.BASIC_AUTHENTICATION,
                "Content-Type": "application/json"
            },
        });
    }

Adding a New Product

Now add the function which creates the product, here we use the admin/api/2021-01/products.json endpoint and receives the product object composed of only the name of the product

static addProduct(data) {
        let product = {
            product:data
        }
        return Http.request({
            url: this.STOREURL + `admin/api/2021-01/products.json`,
            method: 'POST',
            headers: {
                'Authorization': 'Basic ' + this.BASIC_AUTHENTICATION,
                "Content-Type": "application/json"
            },
            content: JSON.stringify(product)
        });
    }

In Vuex add the corresponding addProductTitle action which calls the addProduct function of the ShopifyService class

In this case to keep it simple I create the product with only the name of product

This action creates the product, in turn, adds each product in an array via a mutation

export const addProductTitle = ({ commit }, data) => {
    return new Promise((resolve, reject) => {
        let product = data;
        ShopifyService.addProduct(product).then(function (data) {
            let content = JSON.parse(data.content+"");
            if(data.statusCode == 201){
                commit(types.ADD, {
                    id:content.product.id,
                    title:content.product.title,
                    body_html: content.body_html == null ? '':content.body_html
                })
                resolve(true)
            }
            else
                reject('error')
        });
    });
}

In the main App.vue we add the function to create a product when tapping the floating action button

...
add(){
    let vm = this;
    prompt("Add product").then((result) => {
      if (result.result) {
        this.$store.dispatch("shopifyProducts/addProductTittle", {
            title: result.text,
          }).then(function (data) {
            var toast = Toast.makeText("Added successfully");
            toast.show();
          }).catch(function (data) {
            var toast = Toast.makeText("Error");
            toast.show();
          });
      }
    });
  }
...
image-20210117153958781

Code for Editing and Deleting a Product

In the CardProduct.vue component we add the last two functions they are edit and delete functions

...
edit() {
    let vm = this;
    prompt("Edit title of the product", this.p_product.title).then((result) => {
      if (result.result) {
        store.dispatch("shopifyProducts/updateTitleProduct", {
            id: vm.p_product.id,
            title: result.text,
          })
          .then(function (data) {
            var toast = Toast.makeText("Updated successfully");
            toast.show();
          }).catch(function (data) {
            var toast = Toast.makeText("Error");
            toast.show();
          });
      }
    });
  }

delete_() {
    let vm = this;
    confirm("Delete?").then((result) => {
      if (result) {
        store.dispatch("shopifyProducts/deleteProduct", {
            id: vm.p_product.id,
          }).then(function (data) {
            var toast = Toast.makeText("Deleted successfully");
            toast.show();
          }).catch(function (data) {
            var toast = Toast.makeText("Error");
            toast.show();
          });
      }
    });
  }
...

...And for Vuex we have its corresponding actions which call addProduct and deleteProduct functions of the ShopifyService class

export const addProductTittle = ({ commit }, data) => {
    return new Promise((resolve, reject) => {
        let product = data;
        ShopifyService.addProduct(product).then(function (data) {
            let content = JSON.parse(data.content+"");
            if(data.statusCode == 201){
                commit(types.ADD, {
                    id:content.product.id,
                    title:content.product.title,
                    body_html: content.body_html == null ? '':content.body_html
                })
                resolve(true)
            }
            else
                reject('error')
        });
    });
}

export const deleteProduct = ({ commit }, data) => {
    return new Promise((resolve, reject) => {
        let product = data;
        ShopifyService.deleteProduct(product.id).then(function (data) {
            if(data.statusCode == 200){
                commit(types.DELETE, product)
                resolve(true)
            }
            else
                reject('error')
        });
    });
}

Here we have the said functions in the ShopifyService class

export default class ShopifyService {
...
static updateTitleProduct(product_id, data) {
        let product = {
            product:data
        }
        return Http.request({
            url: this.STOREURL + `admin/api/2021-01/products/${product_id}.json`,
            method: 'PUT',
            headers: {
                'Authorization': 'Basic ' + this.BASIC_AUTHENTICATION,
                "Content-Type": "application/json"
            },
            content: JSON.stringify(product)
        });
    }

    static deleteProduct(product_id) {
        return Http.request({
            url: this.STOREURL + `admin/api/2021-01/products/${product_id}.json`,
            method: 'DELETE',
            headers: {
                'Authorization': 'Basic ' + this.BASIC_AUTHENTICATION, 
                "Content-Type": "application/json"
            },
        });
    }
...

Summary

Shopify is a complete e-commerce platform however it provides APIs to create new functionality. There are a lot of ecommerce needs that you can solve by taking advantage of the Shopify APIs. For example, imagine developing customized behavior around updating products, inventory, customers or even receiving order notifications... but all that in a mobile application.

This was just a simple example about what you can do with Shopify and Nativescript! 💪😄

You can check out the code here