Skip to content

Instantly share code, notes, and snippets.

@sockdrawermoney
Last active May 7, 2020 16:58
Show Gist options
  • Save sockdrawermoney/38f1785aaeb8591a2f161bb840a75e6e to your computer and use it in GitHub Desktop.
Save sockdrawermoney/38f1785aaeb8591a2f161bb840a75e6e to your computer and use it in GitHub Desktop.
Example for gatsby foreignkey relationships across multiple markdown directories

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.

gatsby-config.js (partial)

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/`,
      },
    },
  ],
}

gatsby-node.js

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,
      },
    });
  });
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment