Knowing where to start with Shopify app development can be tricky. Luckily, the Shopify docs have an easy to follow tutorial that we can use to build a QR code app that will link to products. If you know a little React and GraphQL, after we get things up and running you should be able to hit the ground running!
Github: https://github.com/ndrishinski/shopify-sample-app/tree/main
Prefer Video?
Enjoy, otherwise read on!
Shopify CLI
Make sure you have the latest version of Shopify CLI installed by running ‘shopify --version
‘ in your terminal. If not, run ‘npm install -g @Shopify/cli
‘. Additionally make sure you have created a Shopify partner account. Then navigate in the terminal to a directory you’d like to build your app and run ‘shopify app init
‘.
Then give your project a name, pick Remix and JavaScript/Typescript. Now simply ‘ run cd <name-of-app>
‘ and then ‘ shopify app dev
‘. This will start your development server.
Install App in Theme
With you development server running from the last step, just press 'p
‘ in the terminal and it should open a browser window to sign into your Shopify partner account and choose a theme to add this app to.
Lastly click ‘Generate Product’ to create a testing product on your development store.
Edit Code
Now we just need to create and update our Remix app. There are a total of 6 file changes we are going to make. First, open up ‘/prisma/schema.prisma’ and add this code to the bottom:
...
model QRCode {
id Int @id @default(autoincrement())
title String
shop String
productId String
productHandle String
productVariantId String
destination String
scans Int @default(0)
createdAt DateTime @default(now())
}
This creates another database table called QRCode which, you guessed it, will contain our QR code information attributes like the product ID, title, etc.
Now in your terminal open to the project directory, run ‘npm run prisma migrate dev — –name add-qrcode-table’ and then ‘npm run prisma studio’ to create the table and verify in Prisma Studio.
Next create a file named ‘/app/models/QRCode.server.js’ and paste the following code:
import qrcode from "qrcode";
import invariant from "tiny-invariant";
import db from "../db.server";
export async function getQRCode(id, graphql) {
const qrCode = await db.qRCode.findFirst({ where: { id } });
if (!qrCode) {
return null;
}
return supplementQRCode(qrCode, graphql);
}
export async function getQRCodes(shop, graphql) {
const qrCodes = await db.qRCode.findMany({
where: { shop },
orderBy: { id: "desc" },
});
if (qrCodes.length === 0) return [];
return Promise.all(
qrCodes.map((qrCode) => supplementQRCode(qrCode, graphql))
);
}
export function getQRCodeImage(id) {
const url = new URL(`/qrcodes/${id}/scan`, process.env.SHOPIFY_APP_URL);
return qrcode.toDataURL(url.href);
}
export function getDestinationUrl(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`;
}
async function supplementQRCode(qrCode, graphql) {
const qrCodeImagePromise = getQRCodeImage(qrCode.id);
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 qrCodeImagePromise,
};
}
export function validateQRCode(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;
}
}
The purpose of this file is to query the database table we just created!
Next let’s create a file named ‘/app/routes/app.qrcodes.$id.jsx’. And copy / paste this code from Github (for the longer files I will just add links to the Github repo to avoid bloating this article). This file creates a form that the app user (in this case store merchant) can use to manage the QR Codes.
Next let’s create another file in the routes directory: ‘/app/routes/app._index.jsx’. Copy / paste the code from this Github page. The purpose of this page is to allow app users to navigate to QR codes and list the QR codes in the app home.
Now we wil create another file in the routes directory: ‘/app/routes/qrcodes.$id.jsx’. Copy and paste this 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";
export const 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),
});
};
export default function QRCode() {
const { image, title } = useLoaderData();
return (
<>
<h1>{title}</h1>
<img src={image} alt={`QR Code for product`} />
</>
);
}
This code makes QR codes scannable by customers by exposing them using a public URL. When a customer scans a QR code, the scan count should increment, and the customer should be redirected to the destination URL.
And lastly, create one more file in the routes directory: ‘/app/routes/qrcodes.$id.scan.jsx’ and copy / paste this code:
import { redirect } from "@remix-run/node";
import invariant from "tiny-invariant";
import db from "../db.server";
import { getDestinationUrl } from "../models/QRCode.server";
export const 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));
};
Now when a QR code is scanned, redirect the customer to the destination URL.
Test Your New App
Now if your terminal is not running, run ‘shopify app dev’ in your terminal. Press p to open the developer console. In the developer console page, click on the preview link for your app home, and if prompted to install your app click ‘Install’.
Conclusion
This was a quick 5 minutes download of information! I hope this walk through of the Shopify docs App tutorial was helpful in getting you more familiar with the foundations of Shopify app development. I highly recommend taking the time to walk through the Shopify documentation that this walk through is from so you can understand more about the organization of each piece of the app. Stay tuned for more Shopify related content!