🎉 New Integrations Added to the PrestaShop Mobile App!

Vue Storefront Tutorial - Let's start VSF developmet

What we are going to do in this tutorial..

Simply, we want to load the products in our Home page dynamically from our REST API. You can watch the full video, or read this article. After watching/reading this tutorial you will be able to customize and integrate it as you need to have a high performance PWA for your Magento, Shopify, PrestaShop etc, or any other API-based platform. Let's see the whole scenario. If you are not familiar with Headless concept, you can read this article: Why Headless Commerce?

If you are new to Headless concept, you can also check this quick intro. The presentation is prepared for PrestaShop platform but it applies to other platforms too:

Tutorial Scenario

If you run the Vue Storefront boilerplate (think about the boilerplate like a sample and basic shop) you will be faced with a basic shop which its data gets loaded from the local JSON/Mock. There is a section in the Home page that shows a list of products (Match with it). We want to load the data for these products from our API. We call this list, popular products or featured products.

Tutorial Chapters

What is the Vue Storefront integration? Existing Integrations Run Vue Storefront Boilerplate Three main components Api-client package Composables package Theme package Vue Storefront general picture How the Home page is being rendered? Add useProduct to Home page The API that we get data from it Change the api-client package settings Adding featuredProducts API Implementing the useProduct composable How the getFiltered method transforms our data Data mapping and defining product type The MAGIC! How we access our product properties Implement the product getter methods Our Feature is ready :)

What is the Vue Storefront integration?

An integration is a Vue Storefront application which is adapted to your shop's API. If you want to have a modern front-end for your shop for example your Shopify, Magento, PrestaShop or other E-Commerce platforms, you need to integrate Vue Storefront with your platform. So, to use Vue Storefront for your shop you need an integration.

Existing Integrations

You can see a complete list of all integrations in Vue Storefront website. It's a good idea to get idea from them to create your integration. For example you can check other integrations to see how they handled the product detail page to implement yours. Here are the repositories for Odoo and PrestaShop: https://github.com/vuestorefront-community/template-odoo https://github.com/vuestorefront-community/prestashop

Run Vue Storefront Boilerplate

Instead of creating a front application from scratch or considering existing integrations as our base and trying to change them as we need, we use a boilerplate code. This boilerplate code is basic Vue Storefront front application which we consider it as our code base. Let's clone the boilerplate code and run it: https://github.com/vuestorefront/ecommerce-integration-boilerplate New! Boilerplate code has been archived on Mar 24, 2023, I guess a new boilerplate theme will be added.

After cloning the repository, let's rename your project with this command:

grep -rl '__replace_me__' ./ | xargs sed -i '' 's/__replace_me__/{YOUR INTEGRATION NAME}/g'

Change {YOUR INTEGRATION NAME} with your integration name. In our case this is myshop. Now it's time to run the project to test it, let's run the following commands to install dependencies, build the project and run it.

yarn install // to install dependencies
yarn build // to build project
yarn dev // to run project

Once your project is run successfully you will see a Vue Storefront application. Open it in your favorite IDE and navigate through the directories to get familiar with the project.

Three main components

Under packages directory you will see three important packages which are three important Vue Storefront components: api-client, composables and theme

Api-client package

The api-client package is responsible to handle the calling of our back-end APIs. Here is where we define our APIs to send request and get/post data from/to our shop API.

Composables package

Think of Composables package as front business logics. Here we define all logics behind the features, for example what happens when we click on the add to cart button, or how the product functionality works. By default there are some composables which are common in any E-Commerce platform, like product, cart, user, checkout, etc, but if you have any other feature you need to create your own composable.

Theme package

Under the theme directory we have a Nuxt.js application. The Vue Storefront comes with its default UI theme and design system. It uses Storefront UI design system which almost every component in the default theme uses components whose names start with Sf. You can also check its Storybook to find your desired component. Within the theme directory you can see the _theme directory which is the default Vue Storefront theme. To build your custom UI you should override its components and pages. In this tutorial we will override the Home page.

Vue Storefront general picture

Before start coding it's good to see the general picture of Vue Storefront to see how a simple request will be handled. We have three entities: client (which is our browser), middleware server (which our Nuxt.js application and api-client run there) and our back-end API (this can be your Shopify API, Magento API or your custom API).

Let's assume we are accessing the product page in our browser and we need to get the product data. The important point is that the request will be not directly sent to our backend-API (why?), instead it will be sent to our middleware server (api-client), the middleware takes and will handle the rest.

To know why..? that's for Caching, Security, Smaller bundle ect, for more details you can check this article.

How the Home page is being rendered?

Now it's time to see how the Home page is being rendered. This way we can find out how the current product list is built. Navigate to the your_vsf_project/packages/theme/_theme/pages directory. Let's override the Home page component to do our changes. Create a directory named pages at this location: your_vsf_project/packages/theme/pages, and copy the Home.vue file within the pages directory.

Open the Home.vue file, you can see the data method which returns products array. We want to load this array dynamically, and we do not need it. Let's remove the products mock array completely.

Add useProduct to the Home page

To load products we need to use useProduct composable. As you remember the composables are responsible to handle the logic. In Vue Storefront the composables use Composition API which is added in Vue.js 3.0. Thanks to this feature which gives us a good level of code abstraction. To use Composition API we need to add the setup method to our Home page, and get variables from the useProduct composable. First import the composable and some dependencies.

import { computed } from '@vue/composition-api';
import { onSSR } from '@vue-storefront/core';
import {
  useProduct,
  productGetters
} from '@vue-storefront/myshop';

Then add the setup method to Home page component:

  setup() {
    const {
      products: featuredProducts,
      search: productsSearch,
      loading: productsLoading
    } = useProduct();

    onSSR(async () => {
      await productsSearch();
    });
    return {
      products: computed(() =>
        productGetters.getFiltered(featuredProducts.value)
      ),
      productsLoading,
      productGetters
    };
  },

As you see the useProduct returns two properties and one method through which we can handle the product logic in Home page. In Vue Storefront we use onSRR for async calls within setup method. You will read more about getters in the next sections.

The API that we get data from it

Here we use an API for a PrestaShop website. You can download the PrestaShop REST API and install it on your PrestaShop website to test it, or you can use the online demo. This is the postman documentation, which we use the Feature Products API. You can check the API and the structure of the data: https://rest.binshops.com/rest/featuredproducts

Change the api-client package settings

Our api-client will communicate our back-end API through Axios(because our PrestaShop API is REST API, if your API is GraphQL you can install Apolo). Let's install axios, cd to api-client package and run the command:

yarn add axios

Now we need to change the api-client package settings. Under the src directory open the packages/api-client/src/index.server.ts file. Here we need to change the onCreate method, to add our Axios client. onCreate gets the settings parameter which is returned from theme package within the packages/theme/middleware.config.js file. Let's add the Axios client to the index.server.ts file:

import axios from 'axios';
const onCreate = (settings) => {
const client = axios.create({
baseURL: settings.api.url
}); return { config: settings, client }; });

Let's add our base URL which is used during API calls. Open the packages/theme/middleware.config.js and add the URL:

module.exports = {
  integrations: {
    sloth: { // name of your integration
      location: '@myshop/api/server',
      configuration: {
        api: {
          url: 'https://rest.binshops.com' // URL of your eCommerce platform
        }
      }
    }
  }
};

Now let's add featuredProducts API to our api-client package. The featuredProducts API is responsible to call our back-end API (the PrestaShop API) to get the product data. First create the packages/api-client/src/api/featureProducts directory, and create a file named index.ts within it. Add the following function inside it:

export async function featuredProducts(context, params) {
  const url = new URL('/rest/featuredproducts', context.config.api.url);

  // Use axios to send a GET request
  const { data } = await context.client.get(url.href);

  // Return data from the API
  return data;
}

After defining the API we need to export it to be accessible in composable package, open the index.server.ts file again and add featuredProducts API to apiClientFactory:

const { createApiClient } = apiClientFactory<Setttings, Endpoints>({
  onCreate,
  api: {
     getFeaturedProducts 
  }
});

Implementing the useProduct composable

Now it's time to implement the useProduct composable. Let's open the packages/composables/src/useProduct/index.ts file. You see that we need to just implement the productsSearch and the rest will be handled by the useProductFactory. Actually the useProductFactory exports three properties and one method. How the details will be handled? all of the details are implemented inside the useProductFactory. We only need to implement one method. The logic which we should add to this method is very simple. That's just calling our featuredProducts API.

    productsSearch: async (context: Context, params) => {
     const data = await context.$myshop.api.getFeaturedProducts(params);

    return data.psdata;
  }

Note that according to our API JSON structure the list of products are in psdata property.

How the getFiltered method transforms our data

Now the useProduct returns the data from our API. At this step we want to convert it to a type (Product type) which we will use it across the application. Actually we want to transform our platform-specific data structure (in our case the JSON structure which we get from PrestaShop API) to a type which we use it in our Vue Storefront app. We do this transformation with the help of getters. For products we have productGetters which is responsible for this transformation. You will see its magic in the next sections.

Within the productGetters we have a method named getFiltered. This method returns a list of transformed/mapped data. We do this translation with a helper function (enhanceProduct function). Let's open the packages/composables/src/getters/productGetters.ts file and change getFiltered method.

import { enhanceProduct } from '../helpers/internals';
 .
 .
 .
function getFiltered(products, filters: ProductFilter): Product[] {
  if (!products) {
    return [];
  }
  products = Array.isArray(products) ? products : [products];
  return enhanceProduct(products);
}
                        

The getFiltered method will return object of products which the objects are a type of Product type. We will definte the Product in the next section.

Data mapping and defining product type

Now let's create the helper function at this directory: packages/composables/src/helpers/internals/enhanceProduct.ts

const enhanceProduct = (productResponse: Array<any>) => {
  const enhancedProductResponse = productResponse.map((product) => ({
    ...product,
    name: product.name,
    coverImage: product.cover.url,
    regularPrice: product.regular_price_amount,
    discountPrice: product.price_amount
  }));
  return enhancedProductResponse;
};

export default enhanceProduct;
                        

We do this mapping based on our JSON API. To export the helper create the file at: packages/composables/src/helpers/internals/index.ts

import enhanceProduct from './enhanceProduct';

export {
  enhanceProduct
};

Now let's define our Product type in this file at api-client package: packages/api-client/src/types.ts

export type Product = {
  name: string;
  coverImage: string;
  regularPrice: number;
  discountPrice: number;
};

How we access our product properties

Now let's get back to the Home page and shape the component which is responsible to show the products. You remember that we returned the products in setup method. Now the data is correctly fetched from our REST API and is transformed (through the getFiltered method from productGetters), and actually our products data are ready.

Let's check the SfProductCard component, here we loop over the products array and get data for each product. But here something is not normal and actually the magic happens. To get product fields we do not get them directly through its product instance/object. For example, if we want to access the product name, we will not do this: product.name , instead we get it through the getter like this: productGetters.getName(product). We pass the product object to getter methods to get product fields. Now let's fill in the component with the product fields.

<SfCarouselItem class="carousel__item" v-for="(product, i) in products" :key="i">
    <SfProductCard
        :title="productGetters.getName(product)"
        :image="productGetters.getCoverImage(product)"
        :regular-price="$n(productGetters.getPrice(product).regular, 'currency')"
        :link="localePath({ name: 'home' })"
        class="carousel__item__product"
        @click:wishlist="toggleWishlist(i)"
    />
</SfCarouselItem>

Implement the product getter methods

The last step is to implement product getter methods in productGetters file. We just need to have product image, product name and product price. So, we should implement the related methods in productGetters. Let's change the methods in packages/composables/src/getters/productGetters.ts.

  .
  .
function getName(product: Product): string {
  return product?.name || '';
}
  .
  .
function getPrice(product: Product): AgnosticPrice {
  return {
    regular: product?.regularPrice || 0,
    special: product?.discountPrice || 0
  };
}
  .
  .
function getCoverImage(product: Product): string {
  return product.coverImage;
}

Our Feature is ready :)

Now our products carousel is ready. Note that after applying these changes you need to re-run the project.

Article updates

- Initial version Dec 3, 2021
- Nothing has changed with Vue Storefront structure. The content is valid in 2023 as well.
- Boilerplate repository has been archived on Mar 24, 2023.
- Vue Storefront changed to Alokai - 2024

How to Contribute to Open-source projects? Step-by-step Tutorial.

Posted 3 years ago by Sam Berry