GraphQL CMS Tutorial: E-Commerce with GraphCMS, Node.js and Apollo

In a rush? Skip to tutorial steps or Github repo & live demo

Trends... they come and go. Aren't we all tired of seeing those fidget spinners everywhere, right? Chances are we're still stuck with them for a little while, and then we'll never hear from them ever again.

Lately, we've been talking a lot about a trend touching developers with blog posts on JAMstack and static sites CMS. Trust us, we'll continue to talk about it, but today we'll explore something else.

We'll try to capitalize on that spinners trend by setting up our own online shop. To do that we'll play with another new thing that caught our attention and that, on the other end, is probably here to stay: GraphQL.

Since this new query language for API has been released to the public as open source in 2015, by a small company named Facebook (which used it for its mobile apps since 2012), a constantly growing community has been supporting and developing it.

Today we’ll take part in this, while at the same time demonstrating once again how easy it is to integrate our shopping cart Snipcart to any website. This will be accomplished using the GraphQL based headless content management system, GraphCMS.

GraphCMS logo

To get through our task we'll also need the help of NodeJS Express and Apollo. Tutorial step-by-step, live demo and full code repo included, obviously. ;)

Let’s spin our way (see what I did there?) into all of this in order as it follows:

  1. Creating a NodeJS Express app
  2. Setting up GraphCMS
  3. Querying the GraphQL API using Apollo client
  4. Creating our shop with Snipcart

But let’s not get ahead of ourselves right now and let’s, as a young tv star turned international hip hop sensation once said, start from the bottom.

Start with GraphQL

What is GraphQL?

Basically, it is a new syntax for your API that gives instructions on how to fetch data on one or many databases.

It has been created to solve some problems developers encountered when they started to create apps that were way more complex than before.

As with Facebook’s example, they wanted to put all of the website features into the user’s hands, with their mobile apps, back in 2011. That’s when they started to think about a new way to do things. A way that would make the traffic between the clients and the servers simpler and more organized.

Here comes GraphQL.

What is GraphQL

Unlike the REST APIs, they made it possible to manage data over a single endpoint via HTTP with this new query language. Each query you send to your API gets you exactly what you want. What I mean by that is that you will receive on the other end nothing more and nothing less than what you need. The data needed is determined client side instead of letting servers control it, helping to build apps that are way faster and more stable.

Its type schema system regroups all the data you can access, no matter where it is stored, under different fields that you can relate to each other in order to get the information needed in one simple request.

We won’t get into all the specifics here since it’s not the main point of this blog post, but there’s plenty of resources to help if you want to know more.

Here’s a nice tool to help you start with it. To dive deep into it, take a look at the GraphQL specs.

Why GraphQL over REST APIs?

This topic has already caused a lot of discussions on dev's forums, and what you get out of those is that you can’t compare both straight on. They are not the same and GraphQL won’t take over REST APIs tomorrow morning. Whereas the first is, as I already mentioned, a query language, the other is an architectural concept.

You can actually wrap a REST API in GraphQL. This last fact is good to know if you want to try GraphQL without throwing away all your existing infrastructure.

Still, more and more developers will turn towards GraphQL for their new APIs, because it solves a lot of the problems that pushed them to crash their heads through walls, mainly with REST’s multiple endpoints.

REST API vs GraphQL

This aspect meant that you had to make different calls to different endpoints for a single request, such as loading a page, which made the process slower as you create more complex architectures. It can rapidly become a real mess with REST APIs for that reason.

So, what should you do?

It’s entirely up to you, but there are specific situations where GraphQL will particularly fit your needs and where it starts to look like the better option:

  • If you have multiple clients, because they simply write their own queries, in the language of their choice, GraphQL supports them all;
  • If you work on different platforms: web, mobile, apps, etc;
  • If your API is highly customizable.

Speaking of highly customizable, it’s time to see how we can fit Snipcart into all of this, with the help of our friends at GraphCMS. Let's sell some spinners!


GraphQL Tutorial: E-commerce app, using NodeJS Express, GraphCMS, Apollo and Snipcart

In this demo, we'll start a small shop web application from scratch. We'll be using several technologies to achieve this goal.

On the server side of things, we'll go ahead with Express, a minimalist web framework for NodeJS. It's probably the most well-known framework for NodeJS as of now. Although there are some alternatives such as Koa and SailsJS, I decided to go with the classic and stick to what I know best. But please keep in mind that I'm not a NodeJS expert. ;)

We will also be using apollo-client from folks at Apollo, a very neat framework that makes it easy to query a GraphQL API. The GraphQL API will, of course, be handled by GraphCMS.

On top of that, we'll add our e-commerce solution, Snipcart. ;)

Creating the Express app.

We'll start by initializing a new node project.

npm init

Answer the quick questions that the CLI will ask you.

This will create the project.json file and then we'll be ready to start the real things.

We'll first need to add express npm package.

npm install express --save

Then, we'll create the entry file of our application. Let's name it server.js.

// /server.js

const express = require('express')
const app = express()

app.listen(3000, function() {
    console.log('Listening on port 3000.');
})

The first thing we'll do is to set the view engine we are going to use. In our case, we'll go ahead with pug.

npm install pug --save

Then we'll add these lines to the server.js file.

// /server.js

app.set('view engine', 'pug')
app.set('views', path.join(__dirname, '/app/views'))

This will set our default view engine and the path where our views will be located.

We'll then need to create a router that will show the list of our products. Create a new directory named app in your root folder and then create another folder named routers in this newly created directory.

We'll create a router named products.js.

// /app/routers/products.js

const express = require('express')
const router = express.Router()

router.get('/', (req, res) => {
})

We'll need to register this router in our app. Add these lines to server.js file:

// /server.js

const products = require('./app/routers/products')
app.use('/', products)

Setting up GraphCMS

We are now ready to start working with GraphCMS. You will need an account, create a new one here.

Once you are logged in, create a new project, I named mine Snipcart demo. Once the project is created, click on Content and then on Add Content Model.

add-content-model

We'll now define the fields of the product, add the necessary fields so your model looks like this:

product-fields

You can then create a few products. When it's done we'll need to get the GraphQL API endpoint and makes it readable.

Click on Settings in the menu, and enable Public API Access.

public-api-access

Then note your Simple Endpoint URL in the Endpoints section.

Querying our GraphQL API

Now we can go back to the code ;). We'll need to change our products.js router to fetch the items from the API. But before that, we'll create a small service module that will interact with the API using Apollo client.

Let's install the modules we'll need from npm.

npm install apollo-client graphql-tag node-fetch url numeral --save

We also install node-fetch because the fetch method used by apollo-client is not yet available in NodeJS.

url and numeral package will be used for display purposes that we'll see later.

We'll also create a configuration file in our project root that will contain things such as API endpoint URL and Snipcart API key.

// /config.json

{
    "apiEndpoint": "https://api.graphcms.com/simple/v1/cj3c0azkizxa6018532qd1tmh",
    "snipcartApiKey": "MzMxN2Y0ODMtOWNhMy00YzUzLWFiNTYtZjMwZTRkZDcxYzM4"
}

Create a new directory named services in the app folder. We'll add a new file named products.js in it.

// /app/services/products.js

fetch = require('node-fetch')
const config = require('./../../config.json')
const Apollo = require('apollo-client')
const gql = require('graphql-tag')
const ApolloClient = Apollo.ApolloClient
const createNetworkInterface = Apollo.createNetworkInterface


const client = new ApolloClient({
    networkInterface: createNetworkInterface({
        uri: config.apiEndpoint
    })
})

module.exports = {
    getAllProducts: () => {
        return new Promise((resolve, reject) => {
            client.query({
                query: gql`
                   query {
                        allProducts {
                            name,
                            id,
                            sku,
                            price,
                            description,
                            image {
                                url
                            }
                        }
                    }
                `
            })
            .then((response) => {
                resolve(response.data.allProducts)
            })
        })
    },
    getProductById: (id) => {
        return new Promise((resolve, reject) => {
            client.query({
                query: gql`
                    query {
                        Product(id: "${id}") {
                            name,
                            id,
                            image {
                                url
                            },
                            description,
                            price,
                            sku
                        }
                    }
                `
            })
            .then((response) => {
                resolve(response.data.Product)
            })
        })
    }
}

Creating the store

So we now have a way to fetch our data. We can retrieve a list of products and a product by its id. Should be enough for our simple needs in this demo.

Let's start by listing all our products. Open the router file products.js.

// /app/routers/products.js

const express = require('express')
const router = express.Router()
const productsService = require ('./../services/products')

router.get('/', (req, res) => {
    productsService.getAllProducts()
        .then(function (data) {
            res.render('products/index', {
                products: data
            })
        })
})

module.exports = router

We fetch the products from the GraphQL API, then render a view that we'll need to create.

We'll now create the views we need. We'll start by creating our base layout. Create a file named layout.pug in /app/views directory.

// /app/views/layout.pug

html
    head
        if title
            title #{ title }
        else
            title Awesome shop powered by GraphCMS and Snipcart

        block styles
            link(href='https://cdnjs.cloudflare.com/ajax/libs/bulma/0.4.2/css/bulma.min.css', rel='stylesheet', type="text/css")
            link(href="https://cdn.snipcart.com/themes/2.0/base/snipcart.min.css", type="text/css", rel="stylesheet")
            link(href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css', type='text/css', rel='stylesheet')

        block scripts
            script(src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.2/jquery.min.js")
            script(src="https://cdn.snipcart.com/scripts/2.0/snipcart.js", id="snipcart", data-api-key=snipcartApiKey)
        
    body
        .container
            nav.nav.has-shadow
                .nav-left
                    a(href='/').nav-item.is-active.is-tab Spinners

                .nav-right.nav-menu
                    a.nav-item.is-tab.snipcart-summary.snipcart-checkout
                        i.fa.fa-shopping-cart  
                        | View cart (
                        span.snipcart-total-items 0
                        | )
        block content

You will notice that I added some files in there already. First, I included Bulma to make a decent looking application quickly, FontAwesome for the icons and then jQuery and Snipcart required files.

I also added a nav bar with the cart content summary in it.

Now that we have a layout, we'll create the view that will list the products.

Create a new file named index.pug in /app/views/products folder.

// /app/views/products/index.pug

extends ../layout.pug
include ../_mixins/snipcart.pug

block content
    .section
        .container
            .heading
                h1.title Our spinners

    .section
        .container
            .columns.is-multiline
                each p in products
                    .column.is-half
                        article.media
                            figure.media-left
                                p.image.is-64x64
                                    img(src=p.image.url)
                            .media-content
                                .content
                                    p
                                        strong #{ p.name }
                                        small  #{ formatMoney(p.price) }
                                        br
                                        | !{ p.description }
                                nav.level
                                    .level-left
                                        +snipcart_button(p).level-item(title='Add to cart')
                                            span.icon.is-small
                                                i.fa.fa-cart-plus
                                        a(href=`/products/${p.id}`, title='View details').level-item
                                            span.icon.is-small
                                                i.fa.fa-info

This template will render each product, with its details and two buttons, one to get to the product details and another one to add the product to the cart.

You will notice that I used some methods to display things such as formatMoney and a mixin named snipcart_button.

Here's the code for the mixin:

// /app/views/_mixins/snipcart.pug

mixin snipcart_button(product)
    a(href='#').snipcart-add-item&attributes(attributes)(
        data-item-name=product.name
        data-item-id=product.sku
        data-item-price=product.price
        data-item-image=product.image.url
        data-item-url=fullUrl(`/products/${product.id}/json`)
    )
        block

We'll see later what's up with the products/${product.id}/json URL.

The formatMoney will be added to the locals. Open the server.js file and modify it so it looks like this:

// /server.js

const express = require('express')
const app = express()
const products = require('./app/routers/products')
const path = require('path')
const config = require('./config.json')

// We need these two packages for display purposes
const numeral = require('numeral')
const url = require('url')

// We add the methods and properties we need to the response locals.

app.use((req, res, next) => {
    res.locals = {
        snipcartApiKey: config.snipcartApiKey,
        formatMoney: (number) => {
            return numeral(number).format('0.00$')
        },
        fullUrl: (path) => {
            return url.format({
                protocol: req.protocol,
                host: req.get('host'),
                pathname: path
            })
        }
    }
    next()
})

app.use('/', products)

app.use((req, res, next) => {
    res.status(404)
});

app.set('view engine', 'pug')
app.set('views', path.join(__dirname, '/app/views'))

app.listen(3000, function() {
    console.log('Listening on port 3000.');
})

We'll then have access to the Snipcart API key and two helper methods in our templates.

Now, if you type: node server.js in your command prompt you should see something like this:

store

Looking pretty neat! Remember when I said we'll come back to the /json route later? It's now time to deal with it. One feature that we offer that is not used enough, in my opinion, is our JSON crawler. It's very useful when you have access to an API or server side language like we have at the moment.

So we'll create a new route that will return the product details in a JSON format that Snipcart will understand. Open products.js router file and add this route handler:

// /app/routers/products.js

router.get('/products/:id/json', (req, res, next) => {
    res.setHeader('Content-Type', 'application/json')
    productsService.getProductById(req.params.id)
        .then((product) => {
            if (!product) {
                next()
            }
            
            return res.send({
                id: product.sku,
                price: product.price
            })
        })
})

Whenever we'll hit this route our app will return a json object that our crawler will understand.

Since we are in this file, we are also going to add the route to show the single product details template.

// /app/routers/products.js

router.get('/products/:id', (req, res) => {
    productsService.getProductById(req.params.id)
        .then((product) => {
            return res.render('products/details', {
                product: product
            })
        })
})

As we did for the products listing, we'll need to create the products/details.pug view.

// /app/views/products/details.pug

extends ../layout.pug
include ../_mixins/snipcart.pug

block content
    .section
        .container
            .columns
                .card.column.is-half.is-offset-one-quarter
                    .card-image
                        figure.image.is-128x128
                            img(src=product.image.url)
                    .card-content
                        p.title.is-4 #{ product.name }
                            small  #{ formatMoney(product.price) }
                        p #{ product.description }

                    .card-content
                        +snipcart_button(product)
                            span.icon.is-large
                                i.fa.fa-cart-plus                 

We'll be using the same mixin that we used earlier to render the buy button.

That was fun right?

You can now browse our demo website, view product details and add them to the cart. You can find the code here on Github and see the live demo here. Hope you found the fidget spinner you were looking for in there.

GraphCMS makes it very effortless to create easily consumable GraphQL API. The whole demo took me about 2 hours to write, including the learning curve for Apollo and querying a GraphQL API. I sure hope this will help and inspire you to try those new tools! I'm pretty sure that, as developers, we'll all have to work with this new query language in the near future so it's a good thing to start learning about it right now.

If you feel inspired you can visit the GraphQL Awesome list to see all the current possibilities of GraphQL. You can also follow the GraphQL Newsletter to stay put!


If you found this post valuable, please take a second to share it on twitter. Found something we missed? Want to discuss your experience with GraphQL, GraphCMS, NodeJS or Apollo? Comments are all yours!

Suggested posts: