[2024 Remix Edition] Official Shopify App Development Tutorial - Complete Explanation!
Share
In August 2023, with Shopify Edition Summer'23 , the template for Shopify app development was changed to the Remix framework .
You might think that Express and Ruby would continue to coexist in the future, but Remix is now the only option for templates that can be built from the Shopify CLI .
In November 2022, Remix and Shopify partnered, and Remix is essentially becoming a framework exclusively for Shopify development.
In the future, Shopify app development engineers and Shopify custom storefront engineers may find themselves forced to develop with Remix.
So, this time I would like to explain the official Shopify documentation on the Remix framework.
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.
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
=> 3 .24 .1
If you have already installed Shopify CLI, please make sure that it is version 3. If it is version 2, please update it!
Remix is a framework for developing modern web applications, mainly based on React. This framework is officially partnered with Shopify and can be considered the official Shopify framework. The framework adopts many modern best practices and patterns in web development.
Main features
Server-Side Rendering (SSR) : Remix offers a wealth of tools and settings to facilitate server-side rendering, which improves performance and SEO.
Nested Routing : Remix has file-based routing and supports nested routing, making large applications easier to manage.
Data Prefetching : When a user hovers the mouse over a link, the required data is prefetched, making page transitions extremely fast.
Powerful Data Fetching : Remix provides a mechanism to efficiently load the data required for each route.
Code splitting : Allows efficient code splitting, which improves performance.
TypeScript Support : For developers looking for type safety, there's support for TypeScript.
Usage scenarios
High performance websites and applications
SEO focused projects
Large, Complex Applications
Web applications that work closely with APIs
Remix has gained a lot of popularity in a short period of time and has become a powerful tool for efficiently building advanced web applications, especially for developers with experience with React.
Build a Shopify app with Remix
First, decide what kind of app you want to develop.
In this tutorial, we will develop an app that creates a QR code for a product. When a customer scans the QR code, they will be taken to the checkout screen or product detail page where the product is registered.
The app also logs each time a QR code is scanned and shares usage information with merchants.
Through this tutorial you will learn:
What you'll learn
Learn how to use Prisma to manipulate session information and QR code data.
Use the @shopify/shopify-app-remix package to learn how to use the Remix framework to develop applications, authenticate users, and query data.
Shopify App BridgeLearn how to use it to improve your app's speed and UX.
Now that we have a good understanding of the basics, let's get started on development.
First, enter the following command in the terminal to install the Remix Shopify app template.
$ npm init @shopify/app@latest
? Your project name?: プロジェクト名
? Get started building your app:
✔ Start with Remix (recommended)
The latest version of Shopify templates only allow you to choose the Remix framework.
In addition, access to the database (SQLite) has been changed from writing SQL to prisma.
First, check that the installed app runs.
$ cd プロジェクト名
$ npm run dev
? Create this projectas a new app on Shopify?
> **(y) Yes, create it as a new app**
(n) No , connect it to an existing app
? App name :
> **remix-tutorial (app name)**
? Which store would you liketousetoview your project ?
> **※Please make sure to select the test store**
? Have Shopify automatically update your app 's URL in order to create a preview experience?
┃ Current app URL
┃ • <https://shopify.dev/apps/default-app-home>
┃
┃ Current redirect URLs
┃ • <https://shopify.dev/apps/default-app-home/api/auth>
> **(y) Yes, automatically update**
(n) No, never
When you first launch the app, you will be asked which store to install, but be sure to select the test store. The store you select will not be available as a production environment.
This time, I created a new app called " remix-tutorial " in the Partner Dashboard and set it up to automatically update the app URL to the preview URL.
Shown in the terminalPreview URLClick here.
When you click, the app installation screen will be displayed in the test store admin.
Once the installation is complete, you will see the app look like this:
Once installed, the app has links to each documentation and a “Generate a Product” button to create a new demo product.
When you click the button, the GraphQL API code will be displayed and a new product will be registered.
This completes the initial construction of the app! 🎉
thank you for your hard work.
Now, let's get straight into the main tutorial!
1. Add the QR Code data model to the database
💡 Attention!
The prisma and @prisma/client modules are installed with version 4. The latest version is version 5, but you must use version 4.
To store the QR codes, you need to add a table to the database included in the template.
Create this timeQRCodeテーブルThe specifications are as follows:
id : the table's primary key.
title : The name of the QR code specified by the user of your app.
shop : The store that owns the QR code.
productId : The product this QR code applies to.
productHandle : Used to create the destination URL for the QR code.
productVariantId : Used to create the destination URL for the QR code.
destination : The destination where the QR code will be sent.
scans : The number of times the QR code was read.
createdAt : The date and time the QR code was created.
QRCode model contains a key that the app uses to get Shopify product and variant data.
At runtime, additional product and variant properties are retrieved and used to set in the UI.
xxx.server.jsThe filename tells Remix not to include the code in the file in the browser.
In other words, it means " specify it as a file that only works on the server side ."
This is the file you need to write your database requests and any confidential information you don't want to share, such as your private key.
Be careful not to accidentally write your private key on the front end!!! (No matter how many times I tell people to do this, there's always someone who does it...)
Next, the QR code generation moduleqrcodeand the loader can easily throw errors.tiny-invariantInstall.
$ npm i qrcode tiny-invariant
Createdapp/models/QRCode.server.jsWrite the code to get the QR code in the file.
import db from"../db.server" ;import qrcode from"qrcode" ;
import invariant from"tiny-invariant" ;
// Get the QRCode data from the database and return the QRCode data, product information, and link URL.exportasyncfunctiongetQRCode ( id, graphql ) {
const qrCode = await db.qRCode.findFirst({ where : { id } });
if (!qrCode) {
returnnull ;
}
return supplementQRCode(qrCode, graphql);
}
// Returns multiple getQRCodesexportasyncfunctiongetQRCodes ( shop, graphql ) {
const qrCodes = await db.qRCode.findMany({
where : { shop },
orderBy : { id : "desc" },
});
if (qrCodes.length === 0 ) return [];
returnPromise .all(
qrCodes.map( ( qrCode ) => supplementQRCode(qrCode, graphql))
);
}
// Generate QR code image data from the URLexportfunctiongetQRCodeImage ( id ) {
const url = new URL( `/qrcodes/ ${id} /scan` , process.env.SHOPIFY_APP_URL);
return qrcode.toDataURL(url.href);
}
// Generate a product detail page URL or checkout URLexportfunctiongetDestinationUrl ( qrCode ) {
if (qrCode.destination === "product" ) {
return`https:// ${qrCode.shop} /products/ ${qrCode.productHandle} ` ;
}
const match = /gid:\/\/shopify\/ProductVariant\/([0-9]+)/ .exec(qrCode.productVariantId);
invariant(match, "Unrecognized product variant ID" );
return`https:// ${qrCode.shop} /cart/ ${match[ 1 ]} :1` ;
}
// Get product information from the QRCode data via the Graphql API and return it including the link URL.asyncfunctionsupplementQRCode ( qrCode, graphql ) {
const response = await graphql(
`
query supplementQRCode($id: ID!) {
product(id: $id) {
title
images(first: 1) {
nodes {
altText
URL
}
}
}
}
` ,
{
variables : {
id : qrCode.productId,
},
}
);
const {
data : { product },
} = await response.json();
return {
...qrCode,
productDeleted : !product?.title,
productTitle : product?.title,
productImage : product?.images?.nodes[ 0 ]?.url,
productAlt : product?.images?.nodes[ 0 ]?.altText,
destinationUrl : getDestinationUrl(qrCode),
image : await getQRCodeImage(qrCode.id),
};
}
// Implement QRCode validationexportfunctionvalidateQRCode ( data ) {
const errors = {};
if (!data.title) {
errors.title = "Title is required" ;
}
if (!data.productId) {
errors.productId = "Product is required" ;
}
if (!data.destination) {
errors.destination = "Destination is required" ;
}
if ( Object .keys(errors).length) {
return errors;
}
}
Let's take a look at the role of each function defined here.
getQRCode, getQRCodes: Functions to get a single QR code and functions to get multiple QR codes. The specific return value issupplementQRCodeThe return value will be:
getQRCodeImage: Returns a Base 64 encoded QR code image using the qrcode package.
getDestinationUrlWhen a customer scans a QR code, they will be directed to one of two places: a product detail page or checkout with the item in their cart. Create a function to conditionally build this URL depending on the destination the merchant chooses.
supplementQRCode:QRCodeテーブルdoes not contain product information. Therefore, we use the Shopify Admin GraphQL API to get product information using a function that queries the product title, the URL of the first featured product image, and the alternative text. We also use the getDestinationUrl and getQRCodeImage functions we created to get the QR code image of the destination URL, and return an object containing the QR code information and product information.
validateQRCode: Validation functions for title, product ID, and destination.
This completes the creation of the QRCode model!
3. Create a QR code form
Create a form where your app users can manage their QR codes.
To create this formUse Remix Routes, Polaris Components, and App Bridge.
First, the QR code form pageapp.qrcodes.$id.jsxCreate a new one.
$ touch app/routes/app.qrcodes.\ $id .jsx
The backslash before the $ sign is there to prevent the $id from disappearing from the file name .
By the way, there is a special reason why file names have "." (dot) and "$" in them.
When an HTTP request comes in, on the server sideloaderThe function is executed.
In other words, the server side description isloaderThis means that it is done inside a function.
A very similar function,There is also an action function.
The action function is executed on the server side when the HTTP method is other than the GET method, andloaderRuns before any functions, such as form submissions, that are used outside of page load.
loaderWithin the function, user authentication is performed, and the JSON returned to the browser is conditionally branched depending on the value of the id parameter.
To return JSON,json functions are used.jsonThe function has the following capabilities:
useSubmit: Returns a function for submitting the form.
Let's explain what this form state management code means.
errorsIf the user does not fill in all of the QR code form fields,validateQRCodeGet the return value of the function.
formState: The state of the input form.
cleanFormState: The initial state of the form.useLoaderDataAssign the return value of .
isDirty: Determines if the form has been modified.
isSaving,isDeleting:useNavigationUse functions to manage the state of the page.
selectProductTake a look at the following code in the function:
awaitwindow .shopify.resourcePicker
This uses AppBridge's resourcePicker function.
resourcePickerThe function provides a search-based interface that enables users to search for and select one or more products, collections, or product variants, and returns the selected resources to the app.
This functionwindow.shopifycan be obtained from
This makes it easy to retrieve registered product information.
AppBrdige was previously managed by a component. In the Remix framework, component management has been abolished and it is defined as a function of window.shopify. window.shopify is imported at the initial template time.
Now we finally create the form layout.
We will be designing using Polaris components.
Polaris is a design system for the Shopify Admin. Using Polaris components ensures that your UI is accessible, responsive, and appears consistent with the Shopify Admin.
Let's look at what components it consists of from the top.
Breadcrumbs
Title Field
ResourcePicker
Destination options
QR Code Preview
Save and Delete buttons
Polaris makes it easy to build your designs.
We can also see that AppBridge is being used in conjunction with Polaris.
<ui-title-bar>: Use AppBridge to display a title bar that shows whether the user is creating or editing a QR code, and a breadcrumb trail to return to the QR code list.
<Button onClick={selectProduct} id="select-product"> :ButtonIn the component,selectProductDefine a function and call the AppBridge resourcePicker . The retrieved product information isformStatewill be held in
The form layout is now complete.
Click the Save button, and hadleSaveBy executingsubmitThe function is executed.
A POST method HTTP request is sent.
As I mentioned a while ago, server-side processing other than the GET methodactionIt can be defined as a function!
actionThe function handles authentication, validation, saving to the database, and redirection.
Here's what happens:
//...省略// loader関数より前に追記import { json, redirect } from"@remix-run/node" ; // redirectを追記import { getQRCode, validateQRCode } from"~/models/QRCode.server" ; // validateQRCodeを追記import db from"../db.server" ;
// ...省略exportasyncfunctionaction ( { request, params } ) {
const { session } = await authenticate.admin(request);
const { shop } = session;
/** @type {any} */const data = {
...Object.fromEntries( await request.formData()),
shop,
};
if (data.action === "delete" ) {
await db.qRCode.delete({ where : { id : Number (params.id) } });
return redirect( "/app" );
}
const errors = validateQRCode(data);
if (errors) {
return json({ errors }, { status : 422 });
}
const qrCode =
params.id === "new"
? await db.qRCode.create({ data })
: await db.qRCode.update({ where : { id : Number (params.id) }, data });
return redirect( `/app/qrcodes/ ${qrCode.id} ` );
}
//...omission
The action function creates, updates, or deletes a QR code.
To save to DB,db.server.jsFiledbLoading instance.
To use Prisma,db.server.jsIn the file I am instantiating PrismaClient and exporting it.
Your QR code form is now complete!
Start the server and check that it works.
app/qrcodes/new
app/qrcodes/1
I was able to confirm that the QR code generation and saving worked properly.
Next, we will display a list of QR codes.
4. Displaying a list of QR codes
To allow app users to navigate to a QR code, list the QR code on your app's home page.
To read the QR code, go to the app's index rootapp._index.jsxSo, we use the loader function.
Up until now, all the pages we've developed have handled requests from the store admin page.
Here, you will expose the QR code using a public URL so that your customers can scan the QR code.
When a customer scans the QR code, the scan count is incremented and the customer is redirected to the destination URL.
Create a public page.
$ touch app/routes/qrcodes.\ $id .jsx
Renders the title and image data of the QR code.
import { json } from"@remix-run/node" ;
import invariant from"tiny-invariant" ;
import { useLoaderData } from"@remix-run/react" ;
import db from"../db.server" ;
import { getQRCodeImage } from"~/models/QRCode.server" ;
exportconst loader = async ({ params }) => {
invariant(params.id, "Could not find QR code destination" );
const id = Number (params.id);
const qrCode = await db.qRCode.findFirst({ where : { id } });
invariant(qrCode, "Could not find QR code destination" );
return json({
title : qrCode.title,
image : await getQRCodeImage(id),
});
};
exportdefaultfunctionQRCode ( ) {
const { image, title } = useLoaderData();
return (
<>
<h1>{title}</h1>
<img src={image} alt={`QR Code for product`} />
</>
);
}
Where:invariantThe function is a validation function loaded from the tiny-invariant module. It references the value of the first argument and throws an error if it does not exist.
If a matching ID is found, the Remix function will be used to return the QR code as json.
When the user scans the QR code, they will be redirected to the destination URL.
Next, implement the route to the destination URL.
Redirect the customer to a destination URL
Once the QR code is scanned, it redirects the customer to a destination URL. You can also increment the QR code scan count to reflect the number of times the QR code has been used.
First, to create a scanning route, we create a public route that handles scanning the QR code.
$ touch app/routes/qrcodes.\ $id .scan.jsx
loaderUse a function to count QR code scans on the server side and redirect to the destination URL if successful.
import { redirect } from"@remix-run/node" ;
import invariant from"tiny-invariant" ;import db from"../db.server" ;
import { getDestinationUrl } from"../models/QRCode.server" ;
exportconst loader = async ({ params }) => {
invariant(params.id, "Could not find QR code destination" );
const id = Number (params.id);
const qrCode = await db.qRCode.findFirst({ where: { id } });
invariant(qrCode, "Could not find QR code destination" );
await db.qRCode.update({
where: { id },
data: { scans: { increment: 1 } },
});
return redirect(getDestinationUrl(qrCode));
};
If you can't find the QR codeinvariantThe function throws an error, and if found, uses Prisma to find the QRCode tablescansIncrements a column.
That's it, your QRCode generation app is complete!
Let's check if it works without any problems!
Operation check
The operation check is as follows:
Generate a QR code
Download the QR code
Open the public URL of the QR code
Edit your QR code
Delete a QR code
Scan the QR code
Verify that the page redirects to the checkout screen
Once you have logged into the Fly.io dashboard, install the flyctl command .
$ brew install flyctl
flyctlDeploy the app using the command:
$ flyctl launch
? Choose an app name (leave blank to generate one): アプリ名
? SelectOrganization : Fly.ioのアカウント名 (personal)
? Choose a region for deployment: Tokyo, Japan (nrt)
$ flyctl deploy
If the deployment is successful, a domain (https://app name.fly.dev) will be issued.
To register environment variables:flyctlThis can be done by command or on the Fly.io dashboard.
$ fly secrets set SHOPIFY_APP_URL=https://アプリ名.fly.dev SCOPES=write_products NODE_ENV=production SHOPIFY_API_KEY=xxxxxxxxx SHOPIFY_API_SECRET=xxxxxxxx
Go to your Shopify Partner Dashboard, go to "App Settings" for the target app, and switch the "App URL" and "Allowed Redirect URLs" domains to the Fly.io domain.
Now the deployment and configuration is complete!
Fly.io can use SQLite, however in production environments you should avoid using SQLite and use PostgreSQL, MySQL, DynamoDB, etc.
The current setting is still SQLite, so please try changing it to PostgreSQL as an assignment.
This completes the Shopify app development tutorial with Remix framework!
thank you for your hard work!
lastly
To develop a Shopify app, you will need at least the following understanding:
If you try to get customer information via the REST API, the SCOPEread_customersEven if you add it, you will not be able to retrieve it due to a permission error.
This is because, as stated in the Partner Terms and Conditions, prior application and approval is required to obtain customer information.
Even if you are able to retrieve customer information, you will then notice a problem: you can only retrieve a maximum of 50 items.
This is because the REST API has a rate limit of only 50 results by default, so pagination will be required.
To develop a Shopify app like this, you really need a wide range of knowledge.