Modeling A GraphQL API For Your Blog Using Webiny Serverless CMS
This article has been kindly supported by our dear friends at Webiny who help folks to architect, build and deploy solutions on top of serverless infrastructure. Thank you!
In time past, developers reduced the challenges associated with managing content-dependent platforms through the use of Content Management Systems (CMS) which allowed web content to be created and displayed using existing design templates provided by the CMS service.
But with the arrival of Single Page Applications (SPAs), this approach to managing content has become unfavorable as developers are locked-in with the provided design layouts. This is the point where the use of Headless CMS services has been largely embraced as developers have sought more freedom to serve content across various clients such as mobile, web, desktop, and even wearable devices.
A headless CMS stores data in a backend database however unlike the traditional CMS service where content is displayed through a defined template, content is delivered via an API and this gives developers the flexibility to consume content across various clients or frontend frameworks.
One example of such a headless CMS is Webiny. Its serverless headless CMS which provides a personalized admin application to create content, and a robust GraphQL API to consume whatever content was created through the admin application. Further down this article, we will explore Webiny and use the admin app when modeling content through the headless CMS app, then consume the content via the GraphQL API in a Gatsby blog application.
If this is your first time hearing of Webiny, it’s an open-source framework for building serverless applications which provide users with tools and ready-made applications. It has a growing developer community on Slack, ultimately trying to make the development of serverless applications easy and straightforward.
To make this article easy to follow, it has been broken down into two major segments. You can either skip to the part that interests you most, or follow them in the order as they appear below:
- Creating and deploying a Webiny project;
- Integrating the Webiny headless CMS GraphQL API into a Gatsby application as a remote data source.
Note: To follow along, you’ll need to have an AWS account (if not, please create one), Yarn, or have npm installed on your local machine. A good understanding of React.js is beneficial as the demo application is built by using Gatsby.
Creating And Deploying A Webiny Project
To get started, we’re going to create a new Webiny project, deploy it and use the Headless CMS through the generated admin app to begin modeling content within the GraphQL API.
Running the command below from a terminal will generate a new Webiny project based on your answers to the installation prompts:
npx create-webiny-project@beta webiny-blog-backend --tag beta
The command above would run all steps needed for bootstrapping a Webiny project. A Webiny project consists of three smaller applications: a GraphQL API, an admin app, and also a website — all of which are contained in the root generated Webiny project folder similar to the one in the image below.
Next, we need to start the deployment of the three components within the Webiny project to AWS so we can access the GraphQL API. The Cloud Infrastructure section of the Webiny documentation gives a detailed explanation of entire the infrastructure deployed to AWS.
Run the command below from your terminal to begin this deployment which would last for few minutes:
yarn webiny deploy
After a successful deployment of all three apps, the URL to the Admin App, GraphQL API endpoint and website would be printed out in the terminal. You can save them in an editor for later use.
Note: The command above deploys the three generated applications collectively. Please visit this part of the Webiny documentation on instructions on how to deploy the applications individually.
Next, we will be setting up the Headless CMS using the admin application generated for managing your Webiny project.
Webiny Admin App
As part of the first time installation process when you access your admin app, you would be prompted to create a default user with your details, and a password to secure your admin app, after which you proceed through the installation prompts for the Headless CMS, Page Builder and Form Builder.
From the Admin welcome page shown above, navigate to the Content Models page by clicking on the New Content Model button within the Headless CMS card. Being a new project, the Content Models list would be empty, we move on next to create our first Content Model.
For our use-case, each content model would represent a blog post, this means each time we want to create a blog post we would create a content model and the data would be saved into the database and added to GraphQL API.
Clicking the lemon floating action button would display the modal with the fields for creating a new Content Model as shown in the image below.
After creating the content model from the image above, we can open the newly saved content model to begin adding fields containing data about the blog post into the content model.
The Webiny content model page has an easy-to-use drag ‘n’ drop editor which supports dragging fields from the left side and dropping them into the editor on the right side of the page. These fields are of eight categories, each used to hold a specific type of value.
Before we begin adding the fields for the content model, below is a layout of the items we want to be contained in the blog post.
Note: While we do not have to insert the elements in the exact order above, however adding fields is much easier when we have a mental picture of the content model structure.
Add the following items with their appropriate fields into the content editor to create the model structure above.
1. Article Title Item
Starting with the first item in the Article Title, we drag ‘n’ drop the TEXT
field into the editor. The TEXT
field is appropriate for a title as it was created for short texts or single-line values.
Add the Label, Helper Text and Placeholder Text input values into the Field settings modal as shown below.
2. Date Item
Next for the Date, we drag ‘n’ drop the DATE
field into the editor. DATE
fields have an extra date format with options of either date only, time only, date time with timezone, or date time without a given timezone. For our use-case, we will select the date time alongside the timezone option as we want readers to see when the post was created in their current timezone.
3. Article Summary
For the Article summary item, we would drag the LONG TEXT
field into the editor and fill in the Label, Helper Text and Placeholder Text inputs in the field settings. The LONG TEXT
field is used to store multi-line text values and this makes it ideal as the article summary would have several lines summarizing the blog post.
We would use the LONG TEXT
field to create the First Paragraph and Concluding Paragraph items since they all contain a lengthy amount of text values.
4. Sample Image
The FILES
field is used for adding files and object data into the content model. For our use-case, we would add images into the content model using the FILES
field. Drag ‘n’ Drop the FILES
field into the editor for adding images.
After adding all the fields above, click the Preview tab to show the fields input elements added into the content model then fill in the values of these input fields.
From the Preview Tab above, we can see a preview of all model fields dropped into the drag ‘n’ editor for creating a blog post using the content model. Add the respective values into each of the input fields above then click on the Save button at the bottom.
After saving, we can view these input values by querying the GraphQL API using the GraphQL playground. Navigate to the API Information page using the sidebar, to access the GraphQL playground for your project.
Using GraphQL editor, you can inspect the entire GraphQL API structure using the schema introspection feature from the Docs.
We can also create and test GraphQL queries and mutations on our content models using the GraphQL Playground before using them from a client-side application.
Within the image above we used the getContentModel
query from our generated GraphQL API to query our Webiny database for data about the last content model we created. To get this exact model we had to pass in the modelID
of the new model as an argument into the getContentModel
query.
At this point, we have set up our Webiny project and modeled our GraphQL API using the generated Webiny Admin application. We are now left with consuming the GraphQL API from a frontend application as a source of data. The following steps below describe how to consume your GraphQL API within a Gatsby Application.
Generate An API Access Key
All requests made to your Webiny GraphQL API endpoint must contain a valid token within its request headers for authentication. This token is obtained when you generate an API Key.
From the side menu, click the API Keys item within the Security dropdown to navigate to the API Keys page where you create and manage your API Keys for your GraphQL API.
Using the right placed form, we give the new key a name and a description, then we select the All locales radio button option within the Content dropdown. Lastly, within the Headless CMS dropdown, we select the Full Access option from the Access Level dropdown to give this key full access to data within the Headless CMS app of our Admin project.
Note: *When granting app access permission to your API keys, Webiny provides a Custom Access option within the* Access Level dropdown to streamline what the API key can be used for within the selected application.*
After saving the new API Key, a token key would be generated to be used when accessing the API Key. From the image below you can see an example of a token generated for my application within the highlighted box.
Take note of this token key as we would use it next from our Gatsby Web Application.
Setting A Gatsby Single Page Application
Execute the command below to start the installer for creating a new Gatsby project on your local machine using NPM and select your project preference from the installation prompts.
npm init gatsby
Next, run this command to install the following needed dependencies into your Gatsby project;
yarn add gatsby-source-graphql styled-components react-icons moment
To use GraphQL within our Gatsby project, open the gatsby-config.js
and modify it to have the same content with the codes in the code block below;
// gatsby-config.js
module.exports = {
siteMetadata: {
title: "My Blog Powered by Webiny CMS",
},
plugins: [
"gatsby-plugin-styled-components",
"gatsby-plugin-react-helmet",
`gatsby-plugin-styled-components`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images`,
},
},
{
resolve: "gatsby-source-graphql",
options: {
// Arbitrary name for the remote schema Query type
typeName: "blogs",
// Field for remote schema. You'll use this in your Gatsby query
fieldName: "posts",
url: process.env.GATSBY_APP_WEBINY_GRAPHQL_ENDPOINT,
headers : {
Authorization : process.env.GATSBY_APP_WEBINY_GRAPHQL_TOKEN
}
},
},
],
};
Above we are adding an external GraphQL API to Gatsby’s internal GraphQL API using the gatsby-source-graphql plugin. As an extra option, we added the GraphQL endpoint URL and access token value into the request headers from our Gatsby environment variables.
Note: Run the yarn Webiny info
command from a terminal launched within the Webiny project to print out the GraphQL API endpoint used in the url
field of the gatsby-config.js
file above.
When next we start the Gatsby application, our GraphQL schema and data would be merged into Gatsby’s default generated schema which we can introspect using Gatsby’s GraphiQL Playground to see the fields similar to the those in the image below at https://localhost:8000/___graphql
.
Above we tested the Webiny remote schema with a test query alongside exploring the remote schema to see what fields are available within our Gatsby application.
Note: A new test content model was later created to demonstrate multiple content models being returned from the listContentModels
query.
To query and display this data within the Gatsby application, create a new file ( posts.js
) containing the following React component:
import React from "react"
import {FiCalendar} from "react-icons/fi"
import {graphql, useStaticQuery, Link} from "gatsby";
import Moment from "moment"
import {PostsContainer, Post, Title, Text, Button, Hover, HoverIcon} from "../styles"
import Header from "../components/header"
import Footer from "../components/footer"
const Posts = () => {
const data = useStaticQuery(graphql`
query fetchAllModels {
posts {
listContentModels {
data {
name
description
createdOn
modelId
}
}
}
}`)
return (
<div>
<Header title={"Home || Blog"}/>
<div style={{display: "flex", justifyContent: "center",}}>
<PostsContainer>
<div>
<Title align={"center"} bold> A collection of my ideas</Title>
<Text align={"center"} color={"grey"}> A small space to document my thoughts in form of blog posts and articles </Text>
</div>
<br/>
{
data.posts.listContentModels.data.map(({id, name, description, createdOn, modelId}) => (
<Post key={id}>
<div style={{display: "flex"}}>
<HoverIcon>
<FiCalendar/>
</HoverIcon>
<div>
<Text small
style={{marginTop: "2px"}}> {Moment(createdOn).format("dddd, m, yyyy")} </Text>
</div>
</div>
<br/>
<Title bold align={"center"}> {name} </Title>
<br/>
<Text align={"center"}> {description} </Text>
<br/>
<div style={{textAlign: "right"}}>
<Link to={`/${modelId}`} state={{modelId}}>
<Button onClick={_ => {
}}> Continue Reading </Button>
</Link>
</div>
</Post>
))
}
<br/>
</PostsContainer>
</div>
<Footer/>
</div>
)
}
export default Posts
From the code block above, we are making a query using the useStaticQuery hook from Gatsby and we use the returned data to populate the posts within the component styled using styled-components.
Taking a closer look at the Continue Reading button in the code block above, we can see it is wrapped with a link that points to a page’s name of the modelId
currently being iterated over. This page would be created dynamically from a template each time the Gatsby application is started.
To implement this creation of dynamic pages, create a new file (gatsby-node.js
) with the following code.
# gatsby-node.js
const path = require("path")
exports.createPages = async ({graphql, actions, reporter}) => {
const {createPage} = actions
const result = await graphql(`
query getContent {
posts {
listContentModels {
data {
description
createdOn
modelId
name
}
}
}
}`)
// Template to create dynamic pages from.
const blogPostTemplate = path.resolve(`src/pages/post.js`)
result.data.posts.listContentModels.data.forEach(({description, modelId, createdOn, name}) => {
createPage({
path: modelId,
component: blogPostTemplate,
// data to pass into the dynamic template
context: {
name, description, modelId, createdOn
},
})
})
}
As an overview, the code block above adds a new task into our Gatsby Application to be performed immediately after the application is started. At a closer look, we can see the following operations being done while performing this task.
First, we make a GraphQL query to fetch all models created on Webiny which returns an array with the contained fields, then we iterate over the result each time using the createPage API from Gatsby to create a new page dynamically using the component in ./pages/post.js
as a template.
Lastly, we passed in the data that we received from iterating over each object in the Query result into the component being used as a template.
At this point, the template component is non-existent. Create a new file (post.js
) with the code below to create the template.
# ./pages/post.js
import React from "react"
import Moment from "moment"
import Header from "../components/header"
import Footer from "../components/footer"
import {PostContainer, Text, Title} from "../styles";
import Layout from "../components/layout";
const Post = ({ pageContext }) => {
const { name, description , createdOn} = pageContext
return (
<Layout>
<Header title={name}/>
<br/>
<div style={{display: "flex", justifyContent: "center"}}>
<PostContainer>
<Title align={"center"}> {name} </Title>
<Text color={"grey"} align={"center"}>
Created On {Moment(createdOn).format("dddd, mm, yyyy")}
</Text>
<br/>
<Text> {description} </Text>
</PostContainer>
</div>
<br/>
<Footer/>
</Layout>
)
}
export default Post
Above we created a component that is used as a template to create other dynamic pages. This component receives a pageContext
object each time it is used as a template, the fields within the object are further destructured and used to populate the data shown on the page, same as the example shown below.
Conclusion
Within this article we have had a detailed look into what Webiny is, the serverless features it provides, and also how the Headless CMS can be used with a Static Website Generator such as Gatsby as a source of data.
As explained earlier, there are more serverless services which Webiny provides apart from the Headless CMS, such as the No-code Form Builder for building interactive forms, Page Builder, and even a File Manager for use within your applications.
If you are looking for a service to leverage when building your next serverless application, then you should give Webiny a try. You can join the Webiny community on Slack or contribute to the Open Source Webiny project on Github.
References
- Webiny Serverless CMS
- Webiny on GitHub
- Gatsby.js (official website)
- Gatsby.js Docs
- Building A Web App With Headless CMS And React, Blessing Krofegha
Further Reading
- An Introduction To Full Stack Composability
- Headless In Times Of Accessibility
- Building A Stocks Price Notifier App Using React, Apollo GraphQL And Hasura
- Building A Static-First MadLib Generator With Portable Text And Netlify On-Demand Builder Functions