If you’ve been following the headless business ecosystem, you’ve heard some chatter in the community about the updated Shopify Storefront API. The API allows shopkeepers to use their Shopify store as a back-end service to support any front-end application they choose.
This means you can have a Shopify store with all the products and then build your custom e-commerce site with whatever front-end tools you choose (React, Vue, Angular, Next-js, Nuxt, etc.). This allows you to sell products in your Shopify store through other channels, such as mobile apps, online games and web apps.
When we saw the announcement, my team at Netlify decided to use it to spin and build things. The result is five different launch templates –Astro, Nuxt, Gridsome, Eleventy, and Angular– all built with Shopify powered back-end stores. Let’s build another one with SvelteKit!
Set the Shopify
The first thing we should do is to set up a Shopify shop. None of this would have been possible without it. Here’s how you can quickly build one for yourself.
- Create a Shopify Partner account if you don’t already have one
- Log in to your partner account and create a Shopify development store (to test your implementation).
- Generate your API credentials to make authentication requests to your Storefront API
- Create products and product variations in your store. These can be virtual products or real products
- Create a private application on your Shopify management dashboard. This will represent your client application, where you will make the request.
If you do all that, take a break and have a glass of water. And then come back with me. Let’s build this thing!
Set the SvelteKit
To get started with SvelteKit, you may want to take a quick look at the documentation for SvelteKit to see how it works. Otherwise, please stay put and I will guide you through what you need to build this site.
Install and run SvelteKit with the following commands.
npm init svelte@next sveltekit-shopify-demo
cd sveltekit-shopify-demo
npm install
npm run dev -- --open
Copy the code
These commands will do several things for you.
- Create a new SvelteKit project for you
- Install required software packages
- Open the project in your browser,
localhost:3000
Like this.
Ok, it looks like we’re all ready to start editing the project to make it look like the site we want to build. Oh, by the way, this is the project we’re building, if you’d like to have a look.
Ok, let’s start building!
Style design
For convenience, I’ll use a global style file in this project. Open your app.css file and update it with this CSS fragment. That’s styling. All we need to do now is refer to the correct classes in the project file, and the application should do exactly as expected.
Get products from Shopify
What is an e-commerce site without a product, right? I know. If you create your Shopify account and add products, you should be able to see your product list page in your Shopify Admin dashboard.
This is mine. Thanks to my colleague Tara for creating the store and filling it with products so I could use it and pretend I did all the work.
Now all we need to do is make an API call from our SvelteKit application, get all these products from our Shopify store, and display them in our application. Before we do that, let’s talk about certification.
certification
Wouldn’t it be nice to know that the data in your store is protected and only you can access it? Yes. Each Shopify store has credentials that you can use to access them from other apps — in this case, from our SvelteKit app.
If you haven’t already generated credentials for your store, do so now. In your SvelteKit project, create a. Env file and update it like this with your Shopify API key.
VITE_SHOPIFY_STOREFRONT_API_TOKEN = "ADD_YOUR_API_TOKEN_HERE"
VITE_SHOPIFY_API_ENDPOINT = "ADD_YOUR_STORE_API_ENDPOINT_HERE"
Copy the code
Take out your product
Now that we have completed the certification, we can continue to get the product. This might be a good time to let you know that the Shopify Storefront API is just based on GraphQL. This means there is no REST option, so we define GraphQL queries to interact with the API.
Before we capture products, we need a place to store them so that we can use product data elsewhere in our application. That’s what Svelte stores are for. If you want to know more about it, I’ll give you an introduction – read the link information.
Create a store.js file at the root of your project folder and update it with this snippet.
// store.js
import { writable } from 'svelte/store';
import { postToShopify } from '../src/routes/api/utils/postToShopify';
export const getProducts = async () => {
try {
const shopifyResponse = await postToShopify({
query: `{
products(sortKey: TITLE, first: 100) {
edges {
node {
id
handle
description
title
totalInventory
productType
variants(first: 5) {
edges {
node {
id
title
quantityAvailable
price
}
}
}
priceRange {
maxVariantPrice {
amount
currencyCode
}
minVariantPrice {
amount
currencyCode
}
}
images(first: 1) {
edges {
node {
src
altText
}
}
}
}
}
}
}
`
});
return shopifyResponse;
} catch (error) {
console.log(error);
}
};
Copy the code
Ok, what’s that? Let’s take a look. First, we define a getProducts query that asks for the top 100 products in our Shopify store. We then pass the query to our PostToShopify utility function, which receives the query, adds our API key to validate the request, and invokes the Shopify endpoint.
But you may have noticed that the postToShopify function doesn’t exist yet, so let’s go ahead and create it in the project SRC/API /utils folder. If the folder doesn’t exist, you can create it or put functions where you want (just make sure to reference it correctly). I was in this directory: SRC/routes/API/utils/postToShopify. Js.
Update the file with the following snippet.
export const postToShopify = async ({ query, variables }) => { try { const result = await fetch(import.meta.env.VITE_SHOPIFY_API_ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Shopify-Storefront-Access-Token': import.meta.env.VITE_SHOPIFY_STOREFRONT_API_TOKEN }, body: JSON.stringify({ query, variables }) }).then((res) => res.json()); if (result.errors) { console.log({ errors: result.errors }); } else if (! result || ! result.data) { console.log({ result }); return 'No results found.'; } return result.data; } catch (error) { console.log(error); }};Copy the code
Note that I am using the environment variables we set earlier to validate the request we made to Shopify. Be sure to prefix your environment variablesimport.meta.env.
With this done, we can now test our implementation and check if we have successfully fetched the product from our Shopify store. Go to your project SRC /routes/index.svelte and update it with this fragment.
// src/routes/index.svelte <script context="module"> import { products, getProducts } from '.. /.. /store'; export async function load(ctx) { await getProducts(); return { props: { products } }; } </script> <script> export let products; </script> <svelte:head> <title>Home</title> </svelte:head> <section> <h2> {#each $products as product} <p>{product.node.title} </p> {/each} </h2> </section>Copy the code
So what we’re doing here is.
- Get product data from our stores
- will
products
Pass as a prop to the index page - Iterate through the product data and display the name of each product
Let’s check the browser to see if this is the case.
And we do get products from our stores correctly.
Congratulations, we’ve completed our first mission. But of course, we’re doing this just to test our implementation. Let’s create two components (ProductCard.svelte and ProductList.svelte) to help us organize and display the product the way we want.
Create components to organize and display products
Create a component folder in your project SRC folder and add the two files I mentioned above. I’m going to set it up like this.
// src/components/ProductCard.svelte <script> export let product; </script> <section> <div class="product-card"> <div class="product-card-frame"> <a href={`/products/${product.handle}`}> <img class="prodimg" src={product.images.edges[0].node.src} alt={product.handle} /> </a> </div> <div class="product-card-text"> <h3 class="product-card-title">{product.title}</h3> <p class="product-card-description">{product.description.substring(0, 60) + '... '}</p> </div> <a href={`/products/${product.handle}`}> <button>View Item {">"}</button> </a> </div> </section>Copy the code
In this case, we expect a product, an item that will pass in the component from where we rendered it. When we get this item, we extract the different product details we need from it and then use those details to build our product card, as you can see in the clip above.
Let’s do the same for our product list component. Create a productlist. svelte file in the SRC/Components folder and set it like this.
// src/components/ProductList.svelte <script> import ProductCard from '.. /components/ProductCard.svelte'; export let products; </script> <div class="product-grid"> {#each products as product} <ProductCard product={product.node} /> {/each} </div>Copy the code
Here, we receive a Products from our index page (this is where we will render the component) and iterate over the products, rendering a product card for each product. With this, we can go to the index page SRC /routes/index.svelte and render our productList component. Update it with this snippet.
// src/routes/index.svelte <script context="module"> import { products, getProducts } from '.. /.. /store'; export async function load(ctx) { await getProducts(); const productType = ctx.page.query.get('type'); if (productType) { products.update((items) => { const updated = items.filter((product) => product.node.productType === productType); return updated; }); } return { props: { products} }; } </script> <script> import ProductList from '.. /components/ProductList.svelte'; export let products </script> <svelte:head> <title>Shoperoni</title> </svelte:head> <main> <ProductList products={$products} /> </main>Copy the code
Here, we are doing the following.
- Get product data from our stores
- Filter the product list according to the page query (we will do that later
Header
Component uses page queries to filter product lists. - Pass the filtered product list to the page as a prop
- Render our
ProductList
Component and pass product data to it as a prop.
Here it is! When we check in the browser, we should get a better looking product list page.
Set up a product details page
So, we’ve set up our product list page, great! What happens if the user clicks on the View Item in the screenshot above? At the moment, there is nothing. In fact, something will happen: the browser will navigate to the route /products/[the-product-title], and the result will be 404 because the page doesn’t exist yet.
To create a separate product page, let’s update our store.js file and add another query that will take our product handle and use it to get that particular product from our Shopify store.
This will mean that whenever a user visits our individual product page, i.e. /products/aged- Gruyere, the product handle aged- Gruyere will be on that page as page.params.handle. We can then use this handle to query Shopify’s products. Update store.js with this query.
// store.js import { writable } from "svelte/store"; import { postToShopify } from "./src/routes/api/utils/postToShopify"; export const products = writable([]); export const productDetails = writable([]); export const getProducts = async () => { // get products query }; // Get product details export const getProductDetails = async (handle) => { try { const shopifyResponse = await postToShopify({ query: ` query getProduct($handle: String!) { productByHandle(handle: $handle) { id handle description title totalInventory variants(first: 5) { edges { node { id title quantityAvailable priceV2 { amount currencyCode } } } } priceRange { maxVariantPrice { amount currencyCode } minVariantPrice { amount currencyCode } } images(first: 1) { edges { node { src altText } } } } } `, variables: { handle: handle, }, }); productDetails.set(shopifyResponse.productByHandle); return shopifyResponse.productByHandle; } catch (error) { console.log(error); }};Copy the code
Here, we define a new query that will get a specific product based on the product’s handle, which we pass in as a variable to the query. When we call getProductDetails() from our dynamic page and pass the product handle to it, we should get the product data returned.
Ok, let’s create a dynamic page that represents our individual product pages. In the Routes folder, create a new routes/products/[handle].svelte file and set it like this.
// src/routes/products/[handle].svelte <script context="module"> import { productDetails, getProductDetails } from '.. /.. /.. /store'; export async function load(ctx) { let handle = ctx.page.params.handle; await getProductDetails(handle); return { props: { productDetails } }; } </script> <script> export let productDetails; let quantity = 0; let product = $productDetails let productImage = product.images.edges[0].node.src; let productVariants = product.variants.edges.map((v) => v.node); let selectedProduct = productVariants[0].id; const addToCart = async () => { // add selected product to cart try { const addToCartResponse = await fetch('/api/add-to-cart', { method: 'POST', body: JSON.stringify({ cartId: localStorage.getItem('cartId'), itemId: selectedProduct, quantity: quantity }) }); const data = await addToCartResponse.json(); // save new cart to localStorage localStorage.setItem('cartId', data.id); localStorage.setItem('cart', JSON.stringify(data)); location.reload(); } catch (e) { console.log(e); }}; function price(itemPrice) { const amount = Number(itemPrice).toFixed(2); return amount + ' ' + 'USD'; } </script> <main> <! -- page content --> </main>Copy the code
At this point, you may be wondering why we have two
So what we did in the code snippet above is.
- Run during initialization
load()
Function to get a single product from our store - will
productDetails
The object is passed to the page as a prop - Receive on the page
productDetails
The props - right
productDetails
Object to get the data we need on the page
Next, let’s use unstructured product data to build product detail pages.
// src/routes/products/[handle].svelte
<script context="module">
//...
</script>
<script>
//...
</script>
<main class="product-page">
<article>
<section class="product-page-content">
<div>
<img class="product-page-image" src={productImage} alt={product.handle} />
</div>
<div>
<h1>{product.title}</h1>
<p>{product.description}</p>
<form>
{#if productVariants.length > 1}
<div class="product-page-price-list">
{#each productVariants as { id, quantityAvailable, title, priceV2 }}
<div class="product-page-price">
<input
{id}
bind:value={selectedProduct}
type="radio"
name="merchandiseId"
disabled={quantityAvailable === 0}
/>
<label for={id}>
{title} - {price(priceV2.amount)}
</label>
</div>
{/each}
</div>
{:else}
<div class="product-page-price is-solo">
{price(productVariants[0].priceV2.amount)}
</div>
{/if}
<div class="product-page-quantity-row">
<input
class="product-page-quantity-input"
type="number"
name="quantity"
min="1"
max={productVariants[0].quantityAvailable}
bind:value={quantity}
/>
<button type="submit" on:click|preventDefault={addToCart} class="button purchase">
Add to Cart
</button>
</div>
</form>
</div>
</section>
</article>
</main>
Copy the code
Now, if we click the View Item button on the product list page, we should get the individual product details page like this.
! [product details page demo](https://res.cloudinary.com/kennyy/video/upload/v1628962503/product_detail_page_vnozfw.gif)Copy the code
It looks like we’re almost there. Let’s start deploying the site!
Deployed to Netlify
Now that we have a product list page and can view our individual product pages, we can proceed to deploy the site.
To deploy a SvelteKit application, you need to adjust it to the deployment target of your choice. The SvelteKit documentation provides adapters that you can use quickly to deploy your applications. I chose to deploy it to Netlify using the Netlify adapter provided by SvelteKit.
The first thing we need to do is install the Netlify adapter into our SvelteKit project.
npm i -D @sveltejs/adapter-netlify@next
Copy the code
Then, edit your svelte.config.js file and import the Netlify adapter we just installed.
import adapter from '@sveltejs/adapter-netlify';
export default {
kit: {
adapter: adapter(),
target: '#svelte'
}
};
Copy the code
We have installed and configured the adapter in our SvelteKit project. The next thing we need to do is create a netlilify. toml file and set it as follows.
[build] command = "npm run build" publish = "build/" functions = "/functions/" # Svelte requires node v12 [the build environment] NODE_VERSION = "12.20"Copy the code
What we’re doing here is telling Netlify.
- The command to run to build this site is
npm run build
- The directory where the completed site will be located is
/build
- The directory to find our custom Netlify functions is
/functions
(Although we did not use any Netlify functions in this project) - We want it to use Node V12.20 to build websites.
Finally, push the project to Github, then to your Netlify dashboard, and deploy your site from the Github repository you push. If you need help, here’s a one-minute guide to deploying from GitHub to Netlify to guide you. If you want to explore the site, the demo is also hosted on Netlify.
Resources and next steps
We have now completed the construction of a SvelteKit e-commerce site backed by Shopify. In the next tutorial, we’ll link it to more Shopify functionality as we add shopping cart functionality to the site. See you then.
The postBuild an ecommerce site with SvelteKit and the Shopify Storefront APIappeared first onLogRocket Blog.