[2021 Edition] Shopify App Development - A Thorough Explanation of the Official Tutorial

The latest 2023 tutorial is here (2023/09/12)

In August 2023, the framework was changed to Remix.

If you would like to learn the latest version, please check the link below.

[2023 Remix Edition] Official Shopify App Development Tutorial - Complete Explanation!
In August 2023, Shopify Edition Summer'23 changed the template for Shopify app development to the Remix framework. I thought it would continue to coexist with Express and Ruby, but the only template that can be built from the Shopify CLI is Remix...

Click here for the 2022 tutorial (2022/06/26)

[2022 Edition] Thorough Explanation of Shopify App Development Tutorial - Compatible with Shopify CLI 3
The Shopify app development tutorial was revamped in June 2022, so we will thoroughly explain the contents. The Shopify app development template and tutorial were updated in December 2022. Therefore, this article and the latest version (2...

Shopify received a major update at Shopify Unite 2021, and its API documentation was also revamped.

*Further updates were made in June 2022. This article may be out of date. Please check the link above for the latest version.

I have already tested the new tutorial, so I will now explain its contents in detail.

I hope this will be helpful for those who are aspiring to develop Shopify apps in the future.

Shopify Unite
Reimagine commerce and connect with the global community of Shopify developers
Shopify Unite 2022 is coming to a city near you. Meet with Shopify experts and network with peers at tech's premier event for ecommerce developers.
Shopify API Documentation
Build Shopify Apps
Shopify's extensible platform, APIs, and developer tools help you design apps that solve merchant problems at scale.

What are Shopify apps?

Shopify apps allow you to build apps that extend the functionality of your store and create unique shopping experiences for your customers. You can also use data from your Shopify store in your apps and platforms.

Merchants use Shopify apps to fit their specific needs, help them build their business, integrate with third-party services, and add functionality to their Shopify admin.

A diagram showing the relationship between apps, merchants, developers, and customers
Quote: https://shopify.dev/apps/getting-started

The relationship between a Developer (app developer) and a Merchant (store manager) begins when the store manager installs an app provided by the app developer. The store manager provides additional functions to his/her store through the app and improves the customer's shopping experience.

Building the Shopify app environment

Setting up the Shopify app environment is very simple. This blog will explain it on the assumption that you are using Mac OS, so if you are using Windows, please refer to the Shopify documentation.

Install Shopify CLI

 $ brew tap shopify/shopify
 $ brew install shopify-cli
 $ shopify version
 => 2.0 . 2

If you have already installed Shopify CLI, please make sure that the version is 2. If it is 1.4, please update it!

 ※ バージョンが2系になっていない場合
$ brew update
 $ brew upgrade shopify-cli 
$ brew unlink shopify-cli && brew link --force --overwrite shopify-cli 

Create a project

Create a project using the Shopify CLI.

 $ shopify node create --name=アプリ名

The app name is unite_tutorial I decided to do it.

There are two types of apps: public apps and custom apps. Custom apps are used for apps for specific merchants or for development purposes.

If you specify a custom app, then you'll be prompted to log in to your Shopify Partner account.

If your login is successful, you will be redirected to the following screen. You can safely close this page.

When you return to the terminal, your partner account ID will be displayed, so select the account you want to use. (I made one by mistake, so two are displayed.)

Next, select the development store where you want to test your app.

Once completed, it will look like this:

That's all there is to creating the app!

By the way, I just installed the code from GitHub below and set up the app, so if you want to know what kind of app I created specifically, please refer to GitHub.

GitHub - Shopify/shopify-app-template-node
Contribute to Shopify/shopify-app-template-node development by creating an account on GitHub.

Install the app in the store

Let's install the app and check that it works. To run the app, we will use a tool called ngrok.

If you don't have an account, please create one in advance.

Create an ngrok account
ngrok

Next, navigate to the project directory and run it on your local server:

 $ cd アプリ名
$ shopify node serve

If you select "yes", the server will start locally.

Maybe it's just my environment npx browserslist@latest --update-db You are asked to enter the command, so just to be safe, execute it. (I don't really know what this command is, though.)

If the server is running, the app is ready to use. Next, install the app to the development store. Copy the URL in the red frame to access it.

The app installation screen will appear, so install it.

If the app is installed successfully, you're done!

*If it does not display properly, try deleting the app and then reinstalling it.

Shopify app feature development

In this tutorial, we will develop a function that allows merchants to manage their product inventory. Merchants will use the app to register product information such as product name and price.

Inventory management is the process of organizing and controlling stock throughout the supply chain. Effective inventory management is necessary to ensure that businesses have enough stock to meet customer demand.

Register your product

Use the app to register dummy products. To register dummy products, use the following command:

 $ shopify populate products

You will be asked if you want to register to the store you are logged in to, so select yes.

This completes the registration of the dummy product.

Creating a user interface (UI) on the front end

Next, we will test the front end. Shopify's React component library and design system is Polaris. Shopify recommends using Polaris for development to ensure consistency of design across Shopify. Unless there is a special reason, we will use Polaris to develop the front end.

Polaris Documentation
Account connection — Shopify Polaris
The account connection component is used so merchants can connect or disconnect their store to various accounts. For example, if merchants want to use the Faceb...

Using this library, pages/index.js Edit.

pages/index.js
 
import { Heading, Page, TextStyle, Layout, EmptyState} from "@shopify/polaris" ;
 
const img = 'https://cdn.shopify.com/s/files/1/0757/9955/files/empty-state.svg' ;

 const Index = () => (
 < Page > 
< Layout >
 < EmptyState // Empty state component
 heading = "Discount your products temporarily" 
action = {{
 content: ' Select products ',
 onAction: () => this.setState({ open: true }),
 }}
 image={img}
 > 
< p > Select products to change their price temporarily. </ p >
 </ EmptyState >
 </ Layout > 
</ Page >
 );

 export default Index; 

Open the app to check if the front side has changed. If everything went well, you will see the following:

If the server is down, $ shopify node serve Try running it and then accessing it.

This is an image inserted into the Polaris EmptyState component.

Get product information

To get product information, we will use Shopify App Bridge. App Bridge is one of the Shopify libraries that allows you to embed functions directly in the admin screen or customize Shopify UI elements outside of the app. We can implement product retrieval and search functions using the ResourcePicker feature of App Bridge.

pages/index.js
 
import React from 'react' ; 
import { Heading, Page, TextStyle, Layout, EmptyState} from "@shopify/polaris" ;
 import { ResourcePicker, TitleBar } from '@shopify/app-bridge-react' ;

 const img = 'https://cdn.shopify.com/s/files/1/0757/9955/files/empty-state.svg' ;

 // Sets the state for the resource picker 
class Index extends React . Component {
 state = { open : false };
 render() {
 return (
 <Page>
 <TitleBar
 primaryAction= {{ 
content: 'Select products' ,
 onAction: () => this .setState({ open : true }),
 }}
 />
 <ResourcePicker // Resource picker component
 resourceType= "Product"
 showVariants={ false } 
open = { this .state.open }
 onSelection={(resources) => this .handleSelection(resources)}
 onCancel={() => this .setState({ open : false })}
 />
 <Layout>
 <EmptyState
 heading= "Discount your products temporarily" 
action= {{
 content: 'Select products' ,
 onAction: () => this .setState({ open : true }),
 }}
 image={img}
 >
 <p>Select products to change their price temporarily.</p>
 </EmptyState>
 </Layout>
 </Page>
 );
 }
 handleSelection = (resources) => { 
this .setState({ open : false });
 console.log(resources);
 };
 }

 export default Index; 

Using ResourcePicker you can create a display like this:

Explanation of ResourcePicker

@shopify/app-bridge-react It is a component included in.

Configure and customize the following elements:

resourceType [required]: Choose from three types: Product, ProductVariant, and Collection
showVariants: Whether to show product variations. true, false
open [required]: Whether to display the picker. true, false
onSelection: Sets the callback function when a selection is made.
onCancel: Sets the callback function to be called when no selection is made and the picker is closed.

Add a resource list

We were able to retrieve the product list in ResourcePicker, but in order to display detailed information in the app, we need to get the data from the GraphQL Admin API.

  1. Selecting a product using ResourcePicker
  2. Get product details from GraphQL using the selected product ID
  3. Displaying detailed product information obtained from GraphQL

These three steps will display the product details. For more information about the GraphQL Admin API, please refer to the link below.

GraphQL Admin API
GraphQL Admin API reference
The Admin API lets you build apps and integrations that extend and enhance the Shopify admin. Learn how to get started using efficient GraphQL queries.

By the way, we need to temporarily store the information obtained from GraphQL. When we persist data, we save it in a database, but since it is temporary, in this tutorial, store-js I am using the library called. store-js is a cross-browser JavaScript library for managing local storage.

store-js
store-js
A localStorage wrapper for all browsers without using cookies or flash. Uses localStorage, globalStorage, and userData behavior under the hood. Latest version: ...

First, store-js Install. Stop the server and execute the following command.

 $ yarn add store-js

Next, pages In the folder components Create a folder and in it ResourceList.js Let's create a file and write the GraphQL code.

 $ mkdir pages/components
 $ touch pages/components/ResourceList.js

*This tutorial uses Next.js, so you will need to learn Next.js to understand the directory structure.

Created ResourceList.js Put the following code in the file:

pages/components/ResourceList.js
 import React from 'react' ;
 import gql from 'graphql-tag' ;
 import { Query } from 'react-apollo' ;
 import {
 Card,
 ResourceList,
 Stack, 
TextStyle,
 Thumbnail,
 } from '@shopify/polaris' ;
 import store from 'store-js' ;
 import { Redirect } from '@shopify/app-bridge/actions' ;
 import { Context } from '@shopify/app-bridge-react' ; 
import ApplyRandomPrices from './ApplyRandomPrices' ;

 // GraphQL query that retrieves products by ID
 const GET_PRODUCTS_BY_ID = gql`
 query getProducts($ids: [ID!]!) {
 nodes(ids: $ids) {
 ... on Product {
 title
 handle
 descriptionHtml
 id
 images(first: 1 ) {
 edges {
 node {
 originalSrc
 altText
 }
 }
 }
 variants(first: 1 ) {
 edges { 
node {
 price
 id
 }
 }
 }
 }
 }
 }
 `;

 class ResourceListWithProducts extends React . Component {
 static contextType = Context;

 // A constructor that defines selected items and nodes
 constructor (props) { 
super ( props );
 this .state = {
 selectedItems: [],
 selectedNodes: {},
 };
 }

 render() {
 const app = this .context;

 // Returns products by ID
 return ( 
<Query query={GET_PRODUCTS_BY_ID} variables= {{ ids: store. get ( 'ids' ) }} >
 {({ data , loading, error, refetch }) => { // Refetches products by ID
 if (loading) return <div>Loading…</div>; 
if (error) return <div>{error.message}</div>;

 const nodesById = {};
 data .nodes.forEach(node ​​=> nodesById[node.id] = node);

 return (
 <>
 <Card>
 <ResourceList
 showHeader 
resourceName= {{ singular: 'Product' , plural: 'Products' }}
 items={ data .nodes }
 selectable
 selectedItems={ this .state.selectedItems}
 onSelectionChange={selectedItems => {
 const selectedNodes = {};
 selectedItems.forEach(item => selectedNodes[item] = nodesById[item]);
 
return this .setState({
 selectedItems: selectedItems,
 selectedNodes: selectedNodes,
 });
 }}
 renderItem={item => {
 const media = (
 <Thumbnail
 source={
 item.images.edges[ 0 ]
 ? item.images.edges[ 0 ].node.originalSrc 
: ''
 }
 alt={
 item.images.edges[ 0 ]
 ? item.images.edges[ 0 ].node.altText
 : ''
 }
 />
 );
 const price = item.variants.edges[ 0 ].node.price; 
return (
 <ResourceList.Item
 id={item.id}
 media={media}
 accessibilityLabel={`View details for ${item.title}`}
 verticalAlignment= "center"
 onClick={() => {
 let index = this .state.selectedItems.indexOf(item.id);
 const node = nodesById[item.id]; 
if (index === - 1 ) {
 this .state.selectedItems.push(item.id);
 this .state.selectedNodes[item.id] = node;
 } else {
 this .state.selectedItems.splice(index, 1 ); 
delete this .state.selectedNodes[item.id];
 }

 this .setState({
 selectedItems: this .state.selectedItems,
 selectedNodes: this .state.selectedNodes,
 });
 }}
 >
 <Stack alignment= "center" >
 <Stack.Item fill>
 <h3> 
<TextStyle variation= "strong" >
 {item.title}
 </TextStyle>
 </h3>
 </Stack.Item>
 <Stack.Item>
 <p>${price}</p>
 </Stack.Item>
 </Stack>
 </ResourceList.Item>
 );
 }}
 />
 </Card>

 <ApplyRandomPrices selectedItems={ this .state.selectedNodes} onUpdate={refetch} />
 </>
 );
 }}
 </Query>
 );
 }
 }
 
export default ResourceListWithProducts; 
Explanation of ResourceList.js

To enable your app to query data using GraphQL, graphql-tag to your app's file. and react-apollo You need to import

Apollo is a library or platform that makes it easy to work with GraphQL. react-apollo On the front side This is a library that allows you to use apollo-client . If you are interested, please refer to the official Apollo website.

Apollo GraphQL | Supergraph: unify APIs, microservices, & databases in a composable graph
Apollo Graph Platform — unify APIs, microservices, & databases into a graph that you can query with GraphQL

graphql-tag This will convert the string into a GraphQL query. GET_PRODUCTS_BY_ID The constant is a GraphQL query that retrieves product details based on the product ID.

This graphql-tag and react-apollo Let's take a look at the process using

 <Query query={GET_PRODUCTS_BY_ID} variables= {{ ids: store. get ( 'ids' ) }} >
 {({ data , loading, error, refetch }) => {
 ...

GraphQL is executed by setting GET_PRODUCTS_BY_ID as the query in the Query component. variables passes variables to the GraphQL query.

When this process runs, the second argument is loading but true At the completion of the process, loading teeth false It will be.

When the process is complete, the return value is data is stored in. {nodes: [{title: "タイトル", ...}, {...}, ...]} I am getting data in JSON format.

Similarly, if an error occurs during processing, the third argument error If the data is stored in and there are no errors, undefined It is as follows.

Acquired data Contents of Polaris ResourceList It is displayed using a component. ResourceList Component items By entering the retrieved data, it will be displayed in a list.

Using ResourcePicker, we were able to retrieve data and then retrieve detailed product information from GraphQL based on the retrieved data. Finally, if product information has been retrieved, we will display the list, and if no product information is available, we will display the EmptyState component.

pages/index.js
 
import React from 'react' ;
 import { Page, Layout, EmptyState} from "@shopify/polaris" ; 
import { ResourcePicker, TitleBar } from '@shopify/app-bridge-react' ;
 import store from 'store-js' ;
 import ResourceListWithProducts from './components/ResourceList' ;

 const img = 'https://cdn.shopify.com/s/files/1/0757/9955/files/empty-state.svg' ;
 
class Index extends React . Component {
 state = { open : false };
 render() {
 // A constant that defines your app's empty state 
const emptyState = !store. get ( 'ids' );
 return (
 <Page>
 <TitleBar
 primaryAction= {{
 content: 'Select products' ,
 onAction: () => this .setState({ open : true }),
 }}
 /> 
<ResourcePicker
 resourceType= "Product"
 showVariants={ false }
 open = { this .state.open }
 onSelection={(resources) => this .handleSelection(resources)} 
onCancel={() => this .setState({ open : false })}
 />
 {emptyState ? ( // Controls the layout of your app's empty state
 <Layout>
 <EmptyState
 heading= "Discount your products temporarily"
 action= {{
 content: 'Select products' , 
onAction: () => this .setState({ open : true }),
 }}
 image={img}
 >
 <p>Select products to change their price temporarily.</p>
 </EmptyState>
 </Layout>
 ) : (
 // Uses the new resource list that retrieves products by IDs
 <ResourceListWithProducts />
 )}
 </Page>
 );
 }
 handleSelection = (resources) => {
 const idsFromResources = resources.selection.map((product) => product.id); 
this .setState({ open : false });
 store. set ( 'ids' , idsFromResources);
 };
 }

 export default Index; 

This code uses soter-js to separate the display depending on whether product information is included or not. Since it is a simple code, we will omit the explanation.

Update the price of a product

Up to this point, we have completed retrieving and displaying the registered products. Finally, we will add a function to update the price of the displayed products.

Let's write a GraphQL query to randomly update the prices of products. First, create a ApplyRandomPrices.js file in the components folder.

 $ touch pages/components/ApplyRandomPrices.js

Add the following code to the ApplyRandomPrices.js file you created.

pages/components/ApplyRandomPrices.js
import React, { useState } from 'react';
import gql from 'graphql-tag';
import { Mutation } from 'react-apollo'; 
import { Layout, Button, Banner, Toast, Stack, Frame } from '@shopify/polaris' ;
 import { Context } from '@shopify/app-bridge-react' ;

 // GraphQL mutation that updates the prices of products 
const UPDATE_PRICE = gql`
 mutation productVariantUpdate($input: ProductVariantInput!) {
 productVariantUpdate(input: $input) {
 product {
 title
 }
 productVariant {
 id
 price
 }
 }
 }
 ` ;

 class ApplyRandomPrices extends React . Component { 
static contextType = Context;

 render() {
 return ( // Uses mutation's input to update product prices
 < Mutation mutation = {UPDATE_PRICE} >
 {(handleSubmit, {error, data}) => {
 const [hasResults, setHasResults] = useState(false);

 const showError = error && ( 
< Banner status = "critical" > {error.message} </ Banner >
 );

 const showToast = hasResults && (
 < Toast 
content = "Successfully updated"
 onDismiss = {() => setHasResults(false)}
 />
 );

 return (
 < Frame >
 {showToast}
 < Layout.Section >
 {showError} 
</ Layout.Section >

 < Layout.Section >
 < Stack distribution = { " center "}> 
< Button
 primary
 textAlign = { " center " }
 onClick = {() => {
 let promise = new Promise((resolve) => resolve());
 for (const variantId in this.props.selectedItems) { 
const price = Math.random().toPrecision(3) * 10;
 const productVariableInput = {
 id: this.props.selectedItems[variantId].variants.edges[0].node.id,
 price: price,
 };

 promise = promise.then(() => handleSubmit({ variables: { input: productVariableInput }} ));
 }

 if (promise) {
 promise.then(() => this.props.onUpdate().then(() => setHasResults(true)));
 }}
 }
 >
 Randomize prices
 </Button> 
</ Stack >
 </ Layout.Section >
 </ Frame >
 );
 }}
 </ Mutation >
 );
 }
 }

 export default ApplyRandomPrices; 
ApplyRandomPrices.js Explanation of

Up until the point where the GraphQL query is created, it's the same as ResourceList.js . Previously, we used the Query component to retrieve data, but this time we'll use the Mutation component to update data.

The Mutation component defines a handler function as the first argument, and when this function is executed, a mutation (update) is run in GraphQL. The return value is passed as the second argument. It will be passed to {error, data} .

When you press the "Randomize prices" button, a random number is calculated and passed as an argument to the handler function.

Now that the code is complete, let's check that it works!

Operation check

If the amount has changed properly, then you've succeeded!

thoughts

Shopify is updated frequently, so there were various issues, such as the tutorial not working with the latest version. However, this time there were no errors and I was able to proceed smoothly, so it was a very stress-free experience!

I've put together a few points that I found interesting. (Complaints)

  • There's no consistency in using React Hooks sometimes and sometimes not.
  • Isn't @apollo/client easier to use than react-apollo?
  • I'm worried about unnecessary re-rendering.
  • There is no explanation of the libraries used, including Next.js (oversight?)
  • If you don't have knowledge of GraphQL, it's hard to understand

Well, it may be a matter of preference, but...

Overall, I think it was a simple and easy to understand tutorial!

I hope this information will be helpful for those who are aspiring to develop Shopify apps.

Back to blog

Leave a comment

Please note, comments need to be approved before they are published.