This example is for a website project I've been working on for andyet.com that features a mix of markdown folders, including one which pulls a number of resources together via interconnecting "synapses" which are built using gatsby foreign keys.
Sharing it because gatsby foreign keys are not particularly well documented, as discussed here.
module.exports = {
plugins: [
{
resolve: `gatsby-source-filesystem`,
options: {
name: `bios`,
path: `${__dirname}/bios/`,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `brains`,
path: `${__dirname}/brains/`,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `comments`,
path: `${__dirname}/comments/`,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `contributors`,
path: `${__dirname}/contributors/`,
},
},
],
}
const _ = require('lodash');
const path = require('path');
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions;
if (_.get(node, 'internal.type') === `MarkdownRemark`) {
// Get the parent node
// This is different from frontmatter.parent below
const parent = getNode(_.get(node, 'parent'));
// adds a "collection" field to distinguish between md folders
createNodeField({
node,
name: 'collection',
value: _.get(parent, 'sourceInstanceName'),
});
}
};
exports.createSchemaCustomization = ({ actions }) => {
const { createTypes } = actions;
// MarkdownRemark @link(by: "frontmatter.slug", from: "parent")
// SELECT * FROM MarkdownRemark WHERE frontmatter.slug = parent LIMIT 1
//
// [MarkdownRemark] @link(by: "frontmatter.parent", from: "slug")
// SELECT * from MarkdownRemark WHERE frontmatter.parent = slug
//
// [MarkdownRemark] @link(by: "frontmatter.slug", from: "related")
// SELECT * FROM MarkdownRemark WHERE frontmatter.slug = related LIMIT 1
const typeDefs = `
type MarkdownRemark implements Node {
frontmatter: Frontmatter
}
type Frontmatter {
date: Date
name: String
title: String
slug: String
image: String
email: String
twitter: String
location: String
topics: [String]
type: String
priority: Int
page: Int
solo: Boolean
isparent: Boolean
template: String
parent: MarkdownRemark @link(by: "frontmatter.slug", from: "parent")
children: [MarkdownRemark] @link(by: "frontmatter.parent", from: "slug")
related: [MarkdownRemark] @link(by: "frontmatter.slug", from: "related")
order: String
url: String
contributors: [MarkdownRemark] @link(by: "frontmatter.slug", from: "contributors")
author: MarkdownRemark @link(by: "frontmatter.slug", from: "author")
comments: [MarkdownRemark] @link(by: "frontmatter.commenton", from: "slug")
commentby: MarkdownRemark @link(by: "frontmatter.slug", from: "commentby")
}
`;
createTypes(typeDefs);
};
exports.createPages = async ({
actions: { createPage },
graphql,
reporter,
}) => {
const results = await graphql(`
{
everything: allMarkdownRemark(
sort: { fields: [frontmatter___title], order: DESC }
limit: 1000
) {
edges {
node {
fields {
collection
}
id
html
frontmatter {
date
name
title
slug
image
email
twitter
location
topics
priority
template
order
page
solo
isparent
url
parent {
frontmatter {
title
slug
image
}
}
children {
html
frontmatter {
title
slug
image
author {
frontmatter {
name
slug
}
}
page
contributors {
frontmatter {
name
slug
title
url
image
}
}
comments {
html
frontmatter {
date
order
commenton
commentby {
frontmatter {
name
image
title
url
}
}
}
}
}
}
related {
frontmatter {
title
slug
image
contributors {
frontmatter {
name
slug
title
url
image
}
}
}
}
order
contributors {
frontmatter {
name
slug
title
url
image
}
}
author {
frontmatter {
name
slug
}
}
commentby {
frontmatter {
name
slug
title
url
image
}
}
comments {
html
frontmatter {
date
order
commenton
commentby {
frontmatter {
name
slug
title
url
image
}
}
}
}
}
}
}
}
topicsGroup: allMarkdownRemark(limit: 2000) {
group(field: frontmatter___topics) {
fieldValue
}
}
typesGroup: allMarkdownRemark(limit: 2000) {
group(field: frontmatter___type) {
fieldValue
}
}
}
`);
if (results.errors) {
reporter.panicOnBuild(`Error while running GraphQL query.`);
return;
}
const allEdges = results.data.everything.edges;
const bioEdges = allEdges.filter(
edge => edge.node.fields.collection === `bios`
);
const brainEdges = allEdges.filter(
edge => edge.node.fields.collection === `brains`
);
const topics = results.data.topicsGroup.group;
const types = results.data.typesGroup.group;
_.each(brainEdges, (edge, index) => {
const synapse = edge.node;
const previous =
index === brainEdges.length - 1 ? null : brainEdges[index + 1].node;
const next = index === 0 ? null : brainEdges[index - 1].node;
if (synapse.frontmatter.template) {
createPage({
path: `/brains/${synapse.frontmatter.slug}`,
component: path.resolve(
`./src/custom/brains/${synapse.frontmatter.template}`
),
context: {
date: synapse.frontmatter.date,
topics: synapse.frontmatter.topics,
type: synapse.frontmatter.type,
title: synapse.frontmatter.title,
slug: synapse.frontmatter.slug,
previous,
next,
},
});
} else {
createPage({
path: `/brains/${synapse.frontmatter.slug}`,
component: path.resolve('./src/templates/SynapsePage.js'),
context: {
slug: synapse.frontmatter.slug,
previous,
next,
},
});
}
});
_.each(bioEdges, edge => {
const person = edge.node;
createPage({
path: `/team/${person.frontmatter.slug}`,
component: path.resolve('./src/templates/TeamMemberPage.js'),
context: {
slug: person.frontmatter.slug,
},
});
});
topics.forEach(topic => {
createPage({
path: `/brains/topics/${_.kebabCase(topic.fieldValue)}/`,
component: path.resolve('./src/templates/TopicPage.js'),
context: {
topic: topic.fieldValue,
},
});
});
types.forEach(type => {
createPage({
path: `/brains/types/${_.kebabCase(type.fieldValue)}/`,
component: path.resolve('./src/templates/TypePage.js'),
context: {
type: type.fieldValue,
},
});
});
};