├── .gitignore ├── README.md ├── gridsome.config.js ├── package-lock.json ├── package.json ├── src ├── components │ ├── BuyButton.vue │ └── ProductPreview.vue ├── favicon.png ├── layouts │ └── Default.vue ├── main.js ├── pages │ ├── About.vue │ └── Index.vue ├── sources │ └── products │ │ └── index.js └── templates │ └── Product.vue └── static └── logo.svg /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .cache 3 | .DS_Store 4 | src/.temp 5 | node_modules 6 | dist 7 | .env 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Leveraging Vue.js & GraphQL with Gridsome (Airtable Database Example) 2 | 3 | ![gridsome-demo](https://snipcart.com/media/203990/gridsome-airtable-tutorial.png) 4 | 5 | Hard to argue against the fact that Vue.js and GraphQL have made significant waves in the ever-growing JS pond. 6 | 7 | Now, what if I told you that the tool leveraging both to build powerful websites and apps has arrived? Yes, Vue.js finally found its Gatsby, and it’s called Gridsome. 8 | 9 | It quickly caught the attention of our team and here I’m putting it to the test by building my own Vue & GraphQL-powered e-commerce app.This tutorial will highlight some of Gridsome’s neat features while using Airtable as a handcrafted database. 10 | 11 | Steps: 12 | 13 | - Creating a Gridsome project 14 | - Handling a products sheet in Airtable 15 | - Crafting a Gridsome data source plugin to fetch products from Airtable 16 | - Querying data with GraphQL 17 | - Building Vue.js views & listing 18 | - Adding the buy button 19 | 20 | > Read the full tutorial [here](https://snipcart.com/blog/vuejs-graphql-airtable-example) 21 | 22 | > See the live demo [here](https://snipcart-gridsome-airtable.netlify.com/) 23 | 24 | Enjoy folks! 25 | -------------------------------------------------------------------------------- /gridsome.config.js: -------------------------------------------------------------------------------- 1 | // This is where project configuration and installed plugin options are located. 2 | // Learn more: https://gridsome.org/docs/config 3 | 4 | require('dotenv').config(); 5 | 6 | module.exports = { 7 | plugins: [ 8 | { 9 | use: '~/src/sources/products', 10 | options: { 11 | apiKey: process.env.AIRTABLE_KEY, 12 | base: process.env.AIRTABLE_BASE, 13 | }, 14 | }, 15 | ], 16 | }; 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "snipcart-gridsome-airtable-demo", 3 | "private": true, 4 | "scripts": { 5 | "build": "gridsome build", 6 | "develop": "gridsome develop", 7 | "explore": "gridsome explore" 8 | }, 9 | "dependencies": { 10 | "airtable": "^0.5.8", 11 | "dotenv": "^6.2.0", 12 | "gridsome": "^0.4.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/components/BuyButton.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 36 | 37 | 52 | 53 | -------------------------------------------------------------------------------- /src/components/ProductPreview.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 41 | 42 | 65 | 66 | -------------------------------------------------------------------------------- /src/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snipcart/gridsome-graphql-snipcart/2c2e6337bb3aa11a3169db4f54debf111a4d1301/src/favicon.png -------------------------------------------------------------------------------- /src/layouts/Default.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 87 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import DefaultLayout from '~/layouts/Default.vue' 2 | 3 | export default function (Vue, { head, router, isServer }) { 4 | Vue.component('Layout', DefaultLayout) 5 | 6 | head.link.push({ 7 | rel: 'stylesheet', 8 | href: 'https://cdn.snipcart.com/themes/2.0/base/snipcart.min.css' 9 | }); 10 | 11 | head.script.push({ 12 | type: 'text/javascript', 13 | src: 'https://ajax.googleapis.com/ajax/libs/jquery/2.2.2/jquery.min.js', 14 | body: true, 15 | }); 16 | 17 | head.script.push({ 18 | type: 'text/javascript', 19 | src: 'https://cdn.snipcart.com/scripts/2.0/snipcart.js', 20 | body: true, 21 | 22 | // snipcart's attributes 23 | id: 'snipcart', 24 | 'data-api-key': 'MzMxN2Y0ODMtOWNhMy00YzUzLWFiNTYtZjMwZTRkZDcxYzM4', 25 | }); 26 | 27 | head.titleTemplate = "%s – Snipcart's Furniture Store"; 28 | } 29 | -------------------------------------------------------------------------------- /src/pages/About.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 15 | -------------------------------------------------------------------------------- /src/pages/Index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 61 | 62 | 63 | 77 | 78 | 79 | query Products($page: Int) { 80 | allProduct (perPage: 6, page: $page) @paginate { 81 | pageInfo { 82 | totalPages 83 | currentPage 84 | } 85 | edges { 86 | node{ 87 | id, 88 | title, 89 | path, 90 | price, 91 | content, 92 | picture { 93 | thumbnails { 94 | large{ 95 | url 96 | } 97 | } 98 | } 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/sources/products/index.js: -------------------------------------------------------------------------------- 1 | const Airtable = require('airtable'); 2 | 3 | module.exports = function (api, opts) { 4 | const base = new Airtable({apiKey: opts.apiKey}).base(opts.base); 5 | api.loadSource(async store => { 6 | const contentType = store.addContentType({ 7 | typeName: 'Product', 8 | route: '/products/:slug', 9 | }); 10 | 11 | contentType.addSchemaField('price', ({ graphql }) => ({ 12 | type: graphql.GraphQLFloat, 13 | resolve (node) { 14 | return node.fields.unitCost; 15 | } 16 | })); 17 | 18 | await base('Furniture').select().eachPage((records, fetchNextPage) => { 19 | records.forEach((record) => { 20 | const item = record._rawJson; 21 | contentType.addNode({ 22 | id: item.id, 23 | title: item.fields.Name, 24 | fields: item.fields, 25 | content: item.fields.Description, 26 | }); 27 | console.log('Retrieved', record.get('Name')); 28 | }); 29 | 30 | fetchNextPage(); 31 | }); 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /src/templates/Product.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 58 | 59 | 60 | 61 | 86 | 87 | 88 | query Product ($path: String!) { 89 | product(path: $path) { 90 | id, 91 | title, 92 | path, 93 | price, 94 | content, 95 | picture { 96 | thumbnails { 97 | large{ 98 | url 99 | } 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /static/logo.svg: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------