Querying Drupal
Time to take our initial project structure from Lab One and expand upon it, building out some more useful functionality. By the end of this lab, we will have implemented the following functionality:
- Add a few blog posts to Drupal
- Query for all Drupal blog posts on our index page
- Implement a blog post template with Gatsby (a React component!)
- Query for a specific blog post and route to it
As this functionality is fairly complex, we'll be breaking this lab into two parts. Part One will focus on editing the Drupal-exposed CMS for the necessary posts and then displaying those posts from Gatsby with React and GraphQL. Part Two will focus on implementing the page template and displaying a specific blog post. At the outset, you'll have a mostly functional blog powered by Drupal and Gatsby!
Part One
As noted--let's start with editing our Drupal CMS with a few post types. Feel free to use the following as examples, or perhaps write your own, or perhaps even something like Hipster Ipsum
Sample Post One
Far far away, behind the word mountains, far from the countries Vokalia andConsonantia, there live the blind texts. Separated they live in Bookmarksgroveright at the coast of the Semantics, a large language ocean. A small river namedDuden flows by their place and supplies it with the necessary regelialia.## On deer horse aboard tritely yikes and muchThe Big Oxmox advised her not to do so, because there were thousands of badCommas, wild Question Marks and devious Semikoli, but the Little Blind Textdidn’t listen. She packed her seven versalia, put her initial into the belt andmade herself on the way.- This however showed weasel- Well uncritical so misled- this is very interesting- Goodness much until that fluid owlWhen she reached the first hills of the **Italic Mountains**, she had a lastview back on the skyline of her hometown _Bookmarksgrove_, the headline of[Alphabet Village](http://google.com) and the subline of her own road, the LineLane. Pityful a rethoric question ran over her cheek, then she continued herway. On her way she met a copy.### Overlaid the jeepers uselessly much excludingBut nothing the copy said could convince her and so it didn’t take long until afew insidious Copy Writers ambushed her, made her drunk with[Longe and Parole](http://google.com) and dragged her into their agency, wherethey abused her for their projects again and again. And if she hasn’t beenrewritten, then they are still using her.> Far far away, behind the word mountains, far from the countries Vokalia and> Consonantia, there live the blind texts. Separated they live in Bookmarksgrove> right at the coast of the Semantics, a large language ocean.It is a paradisematic country, in which roasted parts of sentences fly into yourmouth. Even the all-powerful Pointing has no control about the blind texts it isan almost unorthographic life One day however a small line of blind text by thename of Lorem Ipsum decided to leave for the far World of Grammar.### According a funnily until pre-set or arrogant well cheerfulThe Big Oxmox advised her not to do so, because there were thousands of badCommas, wild Question Marks and devious Semikoli, but the Little Blind Textdidn’t listen. She packed her seven versalia, put her initial into the belt andmade herself on the way.1. So baboon this2. Mounted militant weasel gregariously admonishingly straightly hey3. Dear foresaw hungry and much some overhung4. Rash opossum less because less some amid besides yikes jeepers freneticimpassive fruitlessly shutWhen she reached the first hills of the Italic Mountains, she had a last viewback on the skyline of her hometown Bookmarksgrove, the headline of AlphabetVillage and the subline of her own road, the Line Lane. Pityful a rethoricquestion ran over her cheek, then she continued her way. On her way she met acopy.> The copy warned the Little Blind Text, that where it came from it would have> been rewritten a thousand times and everything that was left from its origin> would be the word "and" and the Little Blind Text should turn around and> return to its own, safe country.But nothing the copy said could convince her and so it didn’t take long until afew insidious Copy Writers ambushed her, made her drunk with Longe and Paroleand dragged her into their agency, where they abused her for their projectsagain and again. And if she hasn’t been rewritten, then they are still usingher. Far far away, behind the word mountains, far from the countries Vokalia andConsonantia, there live the blind texts.#### Silent delightfully including because before one up barring chameleonSeparated they live in Bookmarksgrove right at the coast of the Semantics, alarge language ocean. A small river named Duden flows by their place andsupplies it with the necessary regelialia. It is a paradisematic country, inwhich roasted parts of sentences fly into your mouth.Even the all-powerful Pointing has no control about the blind texts it is analmost unorthographic life One day however a small line of blind text by thename of Lorem Ipsum decided to leave for the far World of Grammar. The Big Oxmoxadvised her not to do so, because there were thousands of bad Commas, wildQuestion Marks and devious Semikoli, but the Little Blind Text didn’t listen.##### Wherever far wow thus a squirrel raccoon jeez jaguar this from alongShe packed her seven versalia, put her initial into the belt and made herself onthe way. When she reached the first hills of the Italic Mountains, she had alast view back on the skyline of her hometown Bookmarksgrove, the headline ofAlphabet Village and the subline of her own road, the Line Lane. Pityful arethoric question ran over her cheek, then she continued her way. On her way shemet a copy.###### Slapped cozy a that lightheartedly and farThe copy warned the Little Blind Text, that where it came from it would havebeen rewritten a thousand times and everything that was left from its originwould be the word "and" and the Little Blind Text should turn around and returnto its own, safe country. But nothing the copy said could convince her and so itdidn’t take long until a few insidious Copy Writers ambushed her, made her drunkwith Longe and Parole and dragged her into their agency, where they abused herfor their projects again and again.
Sample Post Two
Wow! I love blogging so much already.Did you know that "despite its name, salted duck eggs can also be made fromchicken eggs, though the taste and texture will be somewhat different, and theegg yolk will be less rich."?([Wikipedia Link](http://en.wikipedia.org/wiki/Salted_duck_egg))Yeah, I didn't either.
Use the provided credentials and hosted copy you obtained in Part One and log on to the Drupal CMS.
From here, we'll want to create a few Article
content types (again, feel free to use the examples above or create your own!).
- Log on to your Drupal instance
- Using the toolbar, Content -> Add Content -> Article
- Fill out the details (Markdown is supported and encouraged!)
- Add a featured image
- Save the post
Upon completion, you should see at least one Article type--but feel free to create as many as you want!
We now need to query these blog posts with our exposed GraphQL schema utilizing GrapiQL (a pseudo IDE for editing GraphQL queries).
Using GraphiQL
The first step that needs to be done is start the local development server, which will expose GraphiQL, a live editing build server, and some nice functionality like hot reloading 🔥.
First, ensure you're in the second lab by changing to the directory:
cd labs/02-querying-drupal/ # presuming at the root
Next, start the development server:
yarn start
This will expose a local development server at http://localhost:8000
and it will also expose a GraphQL schema which can be accessed at http://localhost:8000/___graphql
(three underscores!). It will look something like this:
The left pane is the query tab. It can be used to query and then see the results of the query when the "Play" button is pressed. Additionally, the schema (Docs) tab can be opened to inspect the local GraphQL schema to see what's available.
From this point, we want to query for all of our blog posts, so that we can display them on our blog's home page (/
). Play around a bit, and see if you can find the correct query! A hint: Gatsby and the gatsby-plugin-drupal
use a convention of using the prefix all
(e.g. allMarkdownRemark
) to denote all forms of some content.
If you need a little nudge!
query GetAllBlogPosts {allNodeArticle {edges {node {titlefields {slug}}}}}
Editing the Index Page
Now that we have our GraphQL query we can use it to display a listing of all of our blog posts!
We'll be editing a React component and adding a page query. Fun!
Open up src/pages/index.js
, we'll be making all of our changes for Part One within this file.
You'll want to do the following:
- Add the GraphQL query from the previous step (to get all blog posts!)
- Use the injected data in the React component with the
data
prop- Remember that the shape of a GraphQL query directly matches what is available
- e.g.
data.allDrupalBlogPost.edges
will be an array that you can iterate over
- Output a list of blog posts as JSX
- Consider using
ul
andli
component(s) to iterate cleanly over these posts
- Consider using
import React from 'react';import { graphql } from 'gatsby';import Layout from '../components/layout';import SEO from '../components/seo';export default function IndexPage({ data }) {return (<Layout><SEO title="Your Great Blog" description="All my blog posts" />{/* Add the post listing here. Use data! */}</Layout>);}export const indexQuery = graphql`# your query from GraphiQL goes here`;
Need to peek?
import React from 'react';import { graphql } from 'gatsby';import Layout from '../components/layout';import SEO from '../components/seo';export default function IndexPage({ data }) {return (<Layout><SEO title="Your Great Blog" description="All my blog posts" /><ul>{data.allNodeArticle.edges.map(({ node }) => (<li key={node.title}>{node.title}</li>))}</ul></Layout>);}export const indexQuery = graphql`query GetAllBlogPosts {allNodeArticle {edges {node {title}}}}`;
Woo hoo! If you see some posts like below you know that you are officially done with Part One of this lab!
Part Two: The Big Leagues
In Part Two, we want to expand upon some concepts we've touched upon in Part One. Specifically, we'll be invoking some Gatsby APIs and creating pages dynamically. We'll also update our Index page to link to these new components. Let's get to it!
Creating a custom blog post template
First, we need to create our templates directory and our blog post template React component.
mkdir -p src/templatestouch src/templates/article.js
On a non-Unix based filesystem, feel free to just create/edit this file and folder structure with your code editor or manually with the Explorer.
Once created, we'll want to give life to this component. Paste the following content into your version of src/templates/article.js
:
import React from 'react';import Layout from '../components/layout';import SEO from '../components/seo';export default function BlogPost() {return (<Layout><SEO title="Blog post" description="An amazing blog post!" /><h1>This is a blog post!</h1></Layout>);}
Now we need a way to dynamically create pages, and we'll use this template to create those pages. Wouldn't you believe it, we have a way to do this cleanly and easily with Gatsby. Introducing the Node.js API with Gatsby.
gatsby-node.js
This file is where we invoke and expose our Node.js APIs. These are invoked at build-time during the appropriate phase(s). In this part of the lab, we will be using the createPages
API. This API exposes a few utility methods, particularly one called createPage
that will let us dynamically create pages based upon some arbitrary data--e.g. data from our GraphQL schema.
We will want to do several things in this file, particularly with the createPages
API:
- Query for all blog posts (we can re-use something here)
- Use this data to create a page based on a route and our blog post template
const path = require('path');exports.createPages = async function createPages({ actions, graphql }) {const { createPage } = actions;const result = await graphql(`{# your query here# Remember: you want some unique identifier (a slug!) rather than your existing query# GraphiQL is super helpful here :)}`).then(res => {if (res.errors) {throw res.errors;}return res.data;});// iterate over your posts, calling createPage for _each_ post};
consider the following snippet for the createPage
action:
const articleTemplate = path.resolve('src/templates/article.js');const slug = `/blog/1234`;createPage({component: articleTemplate,path: slug,context: {slug,},});
If you need a little... push!
const path = require('path');exports.createPages = async function createPages({ actions, graphql }) {const { createPage } = actions;const result = await graphql(`{# your query here}`).then(res => {if (res.errors) {throw res.errors;}return res.data;});const blogPostTemplate = path.resolve('src/templates/article.js');result.allNodeArticle.edges.forEach(({ node }) => {createPage({component: blogPostTemplate,path: node.fields.slug,context: {slug: node.fields.slug,},});});};
This will create pages at the specified path, that can now be navigated to and will invoke the BlogPost template we previously created. Neat!
src/pages/index.js
Editing We want to be able to route to these posts, so we'll want to edit this page with two tweaks:
- We want to query for a slug, just like we've done in
gatsby-node.js
- We want to use the
Link
component fromgatsby
to link to a post- The
Link
component takes ato
prop, e.g.<Link to="/about">About</About>
will link to/about
. - The
Link
component is imported like so:import { Link } from 'gatsby';
- The
You will be successful if you can (still!) display your listing of all posts, but now clicking one of the posts will link to one of your posts! If you can link to a post, you can move on to the next step! At this point, we want to augment our BlogPost template so that we can actually display a post in more detail!
Updating the BlogPost template
Similarly to page components, template components can also invoke GraphQL queries. If you'll remember, we used a context
argument to our createPage
call. This makes whatever variables you passed available to be queried against. For instance, if we added a slug to our createPage
context argument, we could use it like so:
export const blogPostQuery = graphql`query GetArticleBySlug($slug: String) {# use this $slug somehow :)}`
We will want to use this $slug
variable, and query for a blog post by its slug. A convention in GraphQL is that if all resources are available as allResource
then resource
is the resolver we can use to query a single resource. In other words, if our collection of resources is allNodeArticle
the single resource will be called nodeArticle
. Consider an example:
export const blogPostQuery = graphql`query GetArticleBySlug($slug: String) {nodeArticle({ slug: { eq: $slug }}) {title# whatever else you want!}}`
Specifically, we will want to do the following:
- Import the
graphql
helper fromgatsby
- Add a page query (like above) and query for a blog post by a slug
- GraphiQL is very helpful here!
- Display this data in the BlogPost template
We'll want to render this blog post as HTML. Consider the following query for grabbing HTML content from Markdown.
{# the slug will be a variable in your template, though :)nodeArticle(fields: { slug: { eq: "/some-post" } }) {titlefields {markdownBody {childMarkdownRemark {html}}}}}
Need a little help?
import React from 'react';import { graphql } from 'gatsby';import Layout from '../components/layout';export default function BlogPost({ data }) {const { article } = data;return (<Layout><divdangerouslySetInnerHTML={{__html: article.fields.markdownBody.childMarkdownRemark.html,}}/></Layout>);}export const blogPostQuery = graphql`query GetBlogPostBySlug($slug: String!) {article: nodeArticle(fields: { slug: { eq: $slug } }) {fields {markdownBody {childMarkdownRemark {html}}}}}`;
The end result should be linkable blog posts and blog posts that render their contents. Sounds like... a blog! It's all coming together 🎉