Search code examples
gatsbynetlifynetlify-cms

How to make a one to many connection between Netlify CMS and Gatsby


I'm using Netlify CMS on a gatsby site. I used the netlify CMS relation widget on my post collection to reference the title field of my author collection, so...

- {label: "Author", name: "author", widget: "relation", collection: "authors", searchFields: ["title", "firstName", "lastName"], valueField: "title"}

That works well but when I do a query in my GraphiQL window the only available field is author, with a single string, how do I access all the frontmatter of a particular author?


Solution

  • Making the connection in Netlify turns out to just be the first step. Then you need to configure your gatsby-node.js file to make those relationships on build. The end result allows you to make a query on the posts that has a nested author. Here's the whole setup.

    First the netlifyCMS config.yml which I have in static/admin

    backend:
      name: git-gateway
      branch: development
    
    media_folder: "static/images/uploads" # Media files will be stored in the repo under static/images/uploads
    public_folder: "/images/uploads"
    
        collections:
      - name: "blog" # Used in routes, e.g., /admin/collections/blog
        label: "Blog" # Used in the UI
        folder: "src/pages/blog/posts" # The path to the folder where the documents are stored
        create: true # Allow users to create new documents in this collection
        slug: "{{year}}-{{month}}-{{day}}-{{slug}}" # Filename template, e.g., YYYY-MM-DD-title.md
        fields: # The fields for each document, usually in front matter
          - {label: "Layout", name: "layout", widget: "hidden", default: "blog"}
          - {label: "Author", name: "author", widget: "relation", collection: "authors", searchFields: ["title", "firstName", "lastName"], valueField: "title"}
          - {label: "Title", name: "title", widget: "string"}
          - {label: "Publish Date", name: "date", widget: "datetime"}
          - {label: "Featured Image", name: "thumbnail", widget: "image"}
          - {label: "Body", name: "body", widget: "markdown"}
      - name: "authors"
        label: "Authors"
        folder: "src/authors"
        create: true
        slug: "{{slug}}"
        fields: 
          - {label: "Layout", name: "layout", widget: "hidden", default: "author"}
          - {label: "Full Name", name: "title", widget: "string"}
          - {label: "First Name", name: "firstName", widget: "string"}
          - {label: "Last Name", name: "lastName", widget: "string"}
          - {label: "Position", name: "position", widget: "string"}
          - {label: "Profile Picture", name: "profilePicture", widget: "image"}
          - {label: "Email", name: "email", widget: "string"}
    

    Then at the bottom of my gatsby-node.js file.

    exports.sourceNodes = ({ boundActionCreators, getNodes, getNode }) => {
        const { createNodeField } = boundActionCreators;
    
        const postsOfAuthors = {};
        // iterate thorugh all markdown nodes to link books to author
        // and build author index
        const markdownNodes = getNodes()
            .filter(node => node.internal.type === "MarkdownRemark")
            .forEach(node => {
                if (node.frontmatter.author) {
                    const authorNode = getNodes().find(
                        node2 =>
                            node2.internal.type === "MarkdownRemark" &&
                node2.frontmatter.title === node.frontmatter.author
                    );
    
                    if (authorNode) {
                        createNodeField({
                            node,
                            name: "author",
                            value: authorNode.id,
                        });
    
                        // if it's first time for this author init empty array for his posts
                        if (!(authorNode.id in postsOfAuthors)) {
                            postsOfAuthors[authorNode.id] = [];
                        }
                        // add book to this author
                        postsOfAuthors[authorNode.id].push(node.id);
                    }
                }
            });
    
        Object.entries(postsOfAuthors).forEach(([authorNodeId, postIds]) => {
            createNodeField({
                node: getNode(authorNodeId),
                name: "posts",
                value: postIds,
            });
        });
    };
    

    And lastly in gastby-config.js you need to add the mapping.

        mapping: {
            "MarkdownRemark.fields.author": "MarkdownRemark",
            "MarkdownRemark.fields.posts": "MarkdownRemark",
        }
    

    Which gives you the ability to make a query like tis...

    query AllPosts {
      allMarkdownRemark (
        filter: { fileAbsolutePath: {regex : "\/posts/"} },
        sort: {fields: [frontmatter___date], order: DESC}
      ){
        edges {
          node {
            fields {
              author {
                fields {
                  slug
                }
                frontmatter {
                  title
                  firstName
                  lastName
                  profilePicture
    
                }
              }
              slug
            }
            frontmatter {
              title
              date(formatString:"MMM DD 'YY")
              thumbnail
              layout
            }
            excerpt
          }
        }
      }
    }