Search code examples
graphqlgatsby

Gatsby Frontmatter Validation


I have a few questions regarding the schema customization.

data/categories.json

[
    {
        "name": "Foo Bar",
        "slug": "foo-bar"
    },
    { 
        "name": "Fuz Baz",
        "slug": "fuz-baz"
    }
]

gatsby-config.js

  ...
  
  mapping: {
    "MarkdownRemark.frontmatter.author": `AuthorsJson.alias`,
    "MarkdownRemark.frontmatter.category": `CategoriesJson.name`
  }

gatsby-node.js

exports.createSchemaCustomization = ({ actions, schema }) => {
    const { createTypes } = actions;
    const typeDefs = [
        `type AuthorsJson implements Node @dontInfer {
            alias: String,
            name: String
            slug: String,
            bio: String,
            profile: String
            posts: [MarkdownRemark] @link(by: "frontmatter.author.alias", from: "alias")
        }`,
        `type CategoriesJson implements Node @dontInfer {
            name: String,
            slug: String,
            posts: [MarkdownRemark] @link(by: "frontmatter.category.name", from: "name")
        }`,
    ];

    createTypes(typeDefs);
}

The bottom-line question is, how do I accomplish frontmatter validation?

  1. From the question above, how do I force each post to supply a category AND the category must exist in the categories.json (e.g. they can't make up their own category)?
  2. If front matter is an array, how do I ensure that no more than 3 elements exist in the array?

Thanks!

Option 1:

This is what I have in gatsby-node.js:

var categories = [];
var aliases = [];

exports.onCreateNode = ({ node, getNode, actions }) => {
  const { createNodeField } = actions

  if(node.internal.type === `CategoriesJson`) {
      categories.push(node.name);
  }

  if(node.internal.type === `AuthorsJson`) {
      aliases.push(node.alias);
  }

  if (node.internal.type === `MarkdownRemark`) {
    const slug = createFilePath({ node, getNode, basePath: `pages` })

    createNodeField({
      node,
      name: `slug`,
      value: slug,
    })

    if (node.frontmatter.author === null || node.frontmatter.author === undefined) {
        throw "Page is missing alias: " + node.fileAbsolutePath;
    }

    if (aliases.indexOf(node.frontmatter.author) == -1) {
        throw "Page has invalid alias: " + node.fileAbsolutePath;
    }

    if (node.frontmatter.category === null || node.frontmatter.category === undefined) {
        throw "Page is missing category: " + node.fileAbsolutePath;
    }

    if (categories.indexOf(node.frontmatter.category) == -1) {
        throw "Page has invalid category ('" + node.frontmatter.category +"'): " + node.fileAbsolutePath;
    }
    
    if (node.frontmatter.tags.length > 3) {
        throw "Page has more than 3 tags: " + node.fileAbsolutePath;
    }
  }
}

Is there a better way?


Solution

  • You can simplify all your conditions by:

    let categories = [];
    let aliases = [];
    
    exports.onCreateNode = ({ node, getNode, actions }) => {
      const { createNodeField } = actions
    
      if(node.internal.type === `CategoriesJson`) {
          categories.push(node.name);
      }
    
      if(node.internal.type === `AuthorsJson`) {
          aliases.push(node.alias);
      }
    
      if (node.internal.type === `MarkdownRemark`) {
        const slug = createFilePath({ node, getNode, basePath: `pages` })
    
        createNodeField({
          node,
          name: `slug`,
          value: slug,
        })
    
        if (!node.frontmatter.author || !aliases.includes(node.frontmatter.author)) {
            throw "Page is missing alias: " + node.fileAbsolutePath;
        }
    
    
        if (!node.frontmatter.category || !categories.includes(node.frontmatter.category)) {
            throw "Page is missing category: " + node.fileAbsolutePath;
        }
    
       
        if (node.frontmatter.tags.length > 3) {
            throw "Page has more than 3 tags: " + node.fileAbsolutePath;
        }
      }
    }
    

    I've also added let instead of var, which is kind of deprecated nowadays. Check the difference at https://www.javascripttutorial.net/es6/difference-between-var-and-let/ and replaced the indexOf == -1 for the more semantic includes.

    For the rest, the approach looks quite solid to me, it's a good way.

    If front matter is an array, how do I ensure that no more than 3 elements exist in the array?

    By default (and as it should be) frontmatter will be always an object.