Strapi blog logo
  • Product

      Why Strapi?Content ArchitectureRoadmapIntegrationsTry live demo
      OverviewContent Types BuilderCustomizable APIMedia LibraryRoles & Permissions
      Discover Strapi Enterprise EditionDiscover our partners
  • Pricing

  • Solutions

      Static WebsitesMobile ApplicationsCorporate websitesEditorial SitesEcommerce
      Delivery HeroL'EquipeSociete GeneralePixelDustBanco International
      Discover all our user stories
  • Community

      CommunityWrite for the communityWall of LoveStrapi Conf 2021
      SlackGitHubYoutubeCommunity Forum
      Meet the Strapi Community StarsDiscover the Strapi Showcase
  • Resources

      BlogStartersNewsroomSupport
      Strapi AcademyTutorialsVideosWebinars
      The Guide to Headless CMS Strapi Community Forum
  • Docs

      InstallationConfigurationsDeploymentUpdate versionContent API
      Getting StartedContent ManagerContent-Types BuilderUsers, Roles & PermissionsPluginsSettings
      Developer DocumentationStrapi User Guide

Looking for our logo ?

Logo Brand download
Download Logo Pack
See more Strapi assets
Strapi blog logo
  • Product

    Product

    • Why Strapi?
    • Content Architecture
    • Roadmap
    • Integrations
    • Try live demo

    Features

    • Overview
    • Content Types Builder
    • Customizable API
    • Media Library
    • Roles & Permissions
    • Discover Strapi Enterprise Edition
    • Discover our partners
    Features cover

    Unlock the full potential of content management

    See all features
    Strapi Enterprise Edition

    Discover the advanced features included in Strapi Enterprise Edition.

    Get Started
  • Pricing
  • Solutions

    Solutions

    • Static Websites
    • Mobile Applications
    • Corporate websites
    • Editorial Sites
    • Ecommerce

    Stories

    • Delivery Hero
    • L'Equipe
    • Societe Generale
    • PixelDust
    • Banco International
    • Discover all our user stories
    Delivery Hero team

    Delivery Hero manages their partner portal with Strapi.

    Read their story
    turn 10 studios website

    How 1minus1 delivered a creative website for Turn10 Studios 25% faster with Strapi

    Discover their story
  • Community

    Community

    • Community
    • Write for the community
    • Wall of Love
    • Strapi Conf 2021

    Resources

    • Slack
    • GitHub
    • Youtube
    • Community Forum
    • Meet the Strapi Community Stars
    • Discover the Strapi Showcase
    Strapi Conf

    The first Strapi Global User Conference.

    Register now
    Write for the community

    Contribute on educational content for the community

    Discover the program
  • Resources

    Resources

    • Blog
    • Starters
    • Newsroom
    • Support

    Learning

    • Strapi Academy
    • Tutorials
    • Videos
    • Webinars
    • The Guide to Headless CMS
    • Strapi Community Forum
    Introducing Strapi Academy

    Everything you need to know to master Strapi.

    Go to the academy
    Strapi Repository on GitHub

    Get started with the Strapi repository

    Go to repository
  • Docs

    Developers

    • Installation
    • Configurations
    • Deployment
    • Update version
    • Content API

    User Guide

    • Getting Started
    • Content Manager
    • Content-Types Builder
    • Users, Roles & Permissions
    • Plugins
    • Settings
    • Developer Documentation
    • Strapi User Guide
    Install Strapi

    Install Strapi locally or wherever you need.

    Get Started
    Migration Guides Strapi

    Using a previous version of Strapi? Migrate to the latest.

    Read Guides
Get Started
Back to articles

Building a Jamstack Food Ordering App with Strapi, Gridsome & Snipcart 4/6

Building a Jamstack Food Ordering App with Strapi, Gridsome & Snipcart
  • Share on facebook
  • Share on linkedin
  • Share on twitter
  • Share by email
Ekene Eze

Ekene Eze

December 7, 2020

This article is a guest series by the great Ekene Eze. He’s leading the Developer Experience team at Flutterwave and wrote this blog post through the Write for the Community program.

This series will walk you through the processes of setting up your own food ordering application and by extension any e-commerce app using modern development tools like Strapi, Gridsome and Snipcart.

Table of Contents: 1. Part 1 - Generating a Strapi app and creating products 2. Part 2 - Setting up a Gridsome project 3. Part 3 - Consuming products with Gridsome and GraphQL 4. Part 4 - Creating single product views with Gridsome templates 5. Part 5 - Implementing cart and checkout with Snipcart 6. Part 6 - Deploying the apps

In the last part, we successfully connected our Gridsome application to fetch products from our Strapi backend and display them for users. In this part, we will create single product views with Gridsome templates so that customers can open each product, view more details about it, and also have the opportunity to place orders.

Creating single product views with Gridsome templates

At the moment, we have a product listing page where we can view all products. But clicking on any of the products will lead to a 404 page as we can see below:

Next, let's make it possible for users to view the product pages using Gridsome templates. Gridsome uses templates to create single pages for nodes in a collection. Let's see how that works. In the project's src/templates folder, create a new file called Product.vue and set it up like so:

<!-- src/templates/Product.vue -->
<template>
  <Layout>
    <template>
      <v-card elevation="0" class="mx-auto center mt-5 mb-3" max-width="900">
        <v-img
          class="white--text align-end"
          height="200px"
          width="300px"
          :src="`http://localhost:1337${this.$page.product.image}`"
        >
        </v-img>
        <v-card-title>{{ this.$page.product.title }}</v-card-title>
        <v-card-text class="text--primary">
          <div>{{ this.$page.product.description }}</div>
        </v-card-text>
        <v-card-subtitle class="pb-0">
          <h3>Instructions</h3>
          {{ this.$page.product.instructions }}
        </v-card-subtitle>
        <v-card-actions>
          <v-btn
            rounded
            outlined
            color="orange"
            text
          >
            Add to cart
          </v-btn>
          <v-spacer></v-spacer>
          <v-rating
            :value="this.$page.product.rating"
            color="amber"
            dense
            half-increments
            readonly
            size="14"
          ></v-rating>
          <span>
            <v-card-title>${{ this.$page.product.price }}</v-card-title>
          </span>
        </v-card-actions>
      </v-card>
    </template>
  </Layout>
</template>

<page-query>
query Products($id: ID!) {
  product(id: $id) {
    id
    title
    image
    description
    price,
    instructions,
    rating
  }
}
</page-query>

<script>
export default {};
</script>

In the snippet above, we created a new single view product template. In the template, we use Vuetify's UI components (card, buttons etc) to organize the view of the product's details. Finally, to ensure that we route the appropriate product to its equivalent details page, we write a page query that will return individual product details with respect to the ID we pass into it.

To ensure that this behavior is achieved, we need to update the gridsome.config.js file in the project root directory:

    // gridsome.config.js
    module.exports = {
      siteName: "Gridsome",
      plugins: [],
      templates: {
        Product: "/products/:id",
      },
    };

This is how to set up Gridsome templates and routes. You can learn more about it and other possible configuration option here.

With this, we should be able to route to our individual product pages without hassle. Let's check back on the browser:

Now that we have the template and single page routes setup, let's add the Shop and Support navigations on the header for completeness.

Navigation

In Gridsome, every new file created in the pages directory maps to a new route automatically. This means that in order for us to show different pages for the /shop route and the /support route, all we need to do is create those pages and populate it with our contents of choice. In my case, I will display the same products we have in the homepage in the /shop page and display a contact form in the /support page. Create a new pages/shop.vue file and update it with the snippet below:

    <!-- src/pages/shop.vue -->
    <template>
      <Layout>
        <Products />
      </Layout>
    </template>
    <page-query>
    query{
      products: allProduct{
        edges{
          node{
            id,
            title,
            description,
            rating,
            image,
            price
          }
        }
      }
    }
    </page-query>
    <script>
    import Products from "../components/Products";
    export default {
      components: {
        Products,
      },
    };
    </script>

Basically, what we are doing is rendering the Products component again in this page such that when users navigate to it, they still see a list of products to order. You can organize your app differently, but this should be enough for the scope of this series.

We'll do the same for the /support route, create a new pages/support.vue file. In this file, we are going to display a form where users can type in their questions and send to the site admins. Because I have this habit of implementing validations on my forms, I will use vee-validate to handle validations on the form. Install it with the command below:

npm i vee-validate

Next, open the support.vue file and update it with the snippet below:

    <!-- src/pages/support.vue -->
    <template>
      <Layout>
        <validation-observer ref="observer">
          <form class="center mb-4 mt-3">
            <h3>Contact us</h3>
            <validation-provider
              v-slot="{ errors }"
              name="Name"
              rules="required|max:10"
            >
              <v-text-field
                v-model="name"
                :counter="10"
                :error-messages="errors"
                label="Name"
                required
              ></v-text-field>
            </validation-provider>
            <validation-provider
              v-slot="{ errors }"
              name="email"
              rules="required|email"
            >
              <v-text-field
                v-model="email"
                :error-messages="errors"
                label="E-mail"
                required
              ></v-text-field>
            </validation-provider>
            <validation-provider v-slot="{ errors }" name="select" rules="required">
              <v-select
                v-model="select"
                :items="items"
                :error-messages="errors"
                label="Query type"
                data-vv-name="select"
                required
              ></v-select>
            </validation-provider>
            <v-textarea
              outlined
              name="input-7-4"
              label="What problem do you have"
              value="Tell us more about the problem"
            ></v-textarea>
            <v-textarea
              outlined
              name="input-7-4"
              label="What's your business like"
              value="Tell us more about your business"
            ></v-textarea>
            <v-btn class="mr-4" @click="submit"> Send </v-btn>
            <v-btn @click="clear"> Clear </v-btn>
          </form>
        </validation-observer>
        <div class="separator"></div>
      </Layout>
    </template>
    <script>
    import { required, email, max } from "vee-validate/dist/rules";
    import {
      extend,
      ValidationObserver,
      ValidationProvider,
      setInteractionMode,
    } from "vee-validate";
    setInteractionMode("eager");
    extend("required", {
      ...required,
      message: "{_field_} can not be empty",
    });
    extend("max", {
      ...max,
      message: "{_field_} may not be greater than {length} characters",
    });
    extend("email", {
      ...email,
      message: "Email must be valid",
    });
    export default {
      components: {
        ValidationProvider,
        ValidationObserver,
      },
      data: () => ({
        name: "",
        email: "",
        select: null,
        errors: null,
        items: ["Orders", "Delivery", "Tech support", "Refund"],
        checkbox: null,
      }),
      methods: {
        submit() {
          this.$refs.observer.validate();
        },
        clear() {
          this.name = "";
          this.email = "";
          this.select = null;
          this.checkbox = null;
          this.$refs.observer.reset();
        },
      },
    };
    </script>
    <style>
    
    </style>

We have created a form with fields for the customer's name, email, and query type. We also added text area's for the user to provide more detailed information about their issues. Of course, these are all examples and should be replaced with whatever content you deem more appropriate for your use case. If you are new to vee-validate, it is a Vue.js based framework that makes it easy to validate form inputs and display errors. You can read more about it here.

At this point, if we test out the navigations on the browser, we should get all the new pages we created showing up in their respective routes.

Conclusion

In this part, we've been able to create single product views with Gridsome templates. We've also been able to set up the /shop and /support navigations with the corresponding pages showing on the browser. We also used vee-validate to handle form input validation on the support page. In the next section, we are going to add cart and checkout functionalities to our Gridsome application so that users can indeed order meals from our restaurant. See you there!

  • Share on facebook
  • Share on linkedin
  • Share on twitter
  • Share by email

You might also be interested in...

E-commerce website with Nuxt.js, GraphQL, Strapi and Stripe (4/7)

    E-commerce website with Nuxt.js, GraphQL, Strapi and Stripe (4/7)

    In this tutorial, you will learn how to create a Deliveroo clone using Nuxt.js, GraphQL Stripe and your favorite headless CMS for e-commerce: Strapi. Dishes (4)

    Pierre Burgy

    Pierre Burgy

    July 3, 2018

    Strapi Online Meetup #7 Recap
    • Online Meetup
    • Community

    Strapi Online Meetup #7 Recap

    This is the recap of the online meetup hosted by Daniel and Yves showcasing how Strapi uses Strapi to power strapi.io

    Daniel Madalitso Phiri

    Daniel Madalitso Phiri

    November 23, 2020

    Building a Jamstack Food Ordering App with Strapi, Gridsome & Snipcart 5/6
    • Guides & Tutorials

    Building a Jamstack Food Ordering App with Strapi, Gridsome & Snipcart 5/6

    Learn how to create a food ordering app with Gridsome, Snipcart and your favorite headless CMS: Strapi. Implementing cart and checkout with Snipcart

    Ekene Eze

    Ekene Eze

    December 7, 2020

    Unleash content.

    Starters
    Get Started

    Strapi is the leading open-source Headless CMS. Strapi gives developers the freedom to use their favorite tools and frameworks while allowing editors to easily manage their content and distribute it anywhere.

    Product

    • Why Strapi?
    • Content Architecture
    • Features
    • Enterprise Edition
    • Partner Program
    • Roadmap
    • Support
    • Try live demo
    • Changelog

    Resources

    • How to get started
    • Meet the community
    • Tutorials
    • API documentation
    • GitHub repository
    • Starters
    • Strapi vs Wordpress
    • The Guide to headless CMS

    Integrations

    • All integrations
    • React CMS
    • Next.js CMS
    • Gatsby CMS
    • Vue.js CMS
    • Nuxt.js CMS
    • Gridsome CMS
    • Flutter CMS
    • Hugo CMS
    • Typescript CMS

    Company

    • About us
    • Blog
    • Careers
    • Contact
    • Newsroom
    • Β© 2021, Strapi
    • LicenseTermsPrivacy