Building A Multi Author Blog with Gatsby
Tags: gatsby, graphql
Published: 2019-04-28 19:36:22

Cover Image: [Antique Typewriter](https://unsplash.com/photos/0gkw_9fy0eQ) by [Patrick Fore](https://unsplash.com/@patrickian4)

# Building a multi author blog with Gatsby

**Note** This is a very flat post where I list out all the steps I did to create a multi author blog. You may not want to read this because it can be very boring. It's just not one of those post I give a lot of thought in the creative side of things. 

And if you don't want to read this post but still want the feature, you can check out [this starter](https://github.com/wgao19/gatsby-starter-multiple-author) I created after this post, [demo on Netlify](https://gatsby-starter-multiple-author.netlify.com/).


![](https://thepracticaldev.s3.amazonaws.com/i/a8jrpywlgqsr2vs4feg0.png)

---

So recently I want to create a multi-author blog. Besides marking each post by its own author, I'd like to have a place to display the authors' details as well as being able to query posts by each author on that author's page.

At first it seems to me a fairly common request. "Fairly common" means I expect there is already a starter or a plugin that does that. A few Googling did not suggest desired result. Instead, I see people asking questions and requesting features in Gatsby's GitHub repo. Not a good sign 🙈 I remember for a fact that Gatsby accepts a `mapping` in `gatsby-config` which allows us to add a mapping to author details. So adding a couple of pages on top of that should follow directly, isn't it?

To verify whether my naive idea will work or not, I decided to give it my own shot and see how.

_Spoiler_: The feature is possible, but not without a hiccup. I guess that's the fun part.

## Implementation

In this post, we'll cover the following topics:
- Map `author` from front matter to an author details specified with YAML
- Create all authors page
- Create author detail pages

### Map the `author` field in posts' front matter

So we want to create a mapping between an author id and author details. Gatsby supports creating mapping between node types. We can specify that inside `gatsby-config.js` with an optional field called `mapping`, [docs here](https://www.gatsbyjs.org/docs/gatsby-config/#mapping-node-types). It accepts mappings defined in a single file, or multiple files with a mapping scheme on file names. It also supports multiple file types such as YAML, JSON, markdown, etc.

We'll follow a common practice that specifies multi author information with YAML.

In order to parse the YAML file, add [`gatsby-transformer-yaml`](https://www.gatsbyjs.org/packages/gatsby-transformer-yaml/?=transformer-yaml) to your package.

```shell
$ yarn add gatsby-transformer-yaml
```

And in the plugins

```js
// gatsby-config.js
module.exports = {
  plugins: [`gatsby-transformer-yaml`],
}
```

Note here that the file we later create needs to live in a directory that gets picked up by the file source plugin. If you intend to create a separate directory, say `mappings`, you may need to:

```js
// gatsby-config.js
module.exports = {
  plugins: [
    `gatsby-transformer-yaml`,
    {
      resolve: 'gatsby-source-filesystem',
      options: {
        name: 'mappings',
        path: `${__dirname}/mappings/`,
      },
    },
  ],
}
```

Then, to tell Gatsby to map author to author detail, specify `mapping` as a field in your configurations like:

```js
// gatsby-config.js
module.exports = {
  mapping: {
    'MarkdownRemark.frontmatter.author': `AuthorYaml`,
  },
  plugins: [`gatsby-transformer-yaml`],
}
```

Next, create the `author.yaml` file that contains the author details. Take note that the this file needs to be picked up by `gatsby-source-filesystem`. Consider putting it under the root of your blogs directory. With `gatsby-advanced-starter` I'm using, I put it under the directory `/content`.

```yaml
# content/author.yaml

- id: Curious Cat
  bio: Very curious about this world and blogging whenever learning something new
  twitter: 'nonexistencecuriouscat'

- id: Wei Gao
  bio: First curious cat at Gatsby Curious Community. Blogs at dev.wgao19.cc.
  twitter: 'wgao19'
```

With the above mapping set up, Gatsby will now treat the `author` field you specify inside our posts' `frontmatter` as the id to an author. And it will map it to the actual details of that author in the `author.yaml` file.

Now, the `author` field in `frontmatter` is no longer a string, but of shape specified in our authors mapping. To pick up the author information in a post, update the GraphQL queries when needed. Once again with the `gatsby-advanced-starter` I'm using, this would come down to:

```diff
// templates/post.jsx

export const pageQuery = graphql`
  query BlogPostBySlug($slug: String!) {
    markdownRemark(fields: { slug: { eq: $slug } }) {
      html
      timeToRead
      excerpt
      frontmatter {
        title
        cover
        date
        category
        tags
-       author
+       author {
+         id
+         bio
+         twitter
+       }
      }
      fields {
        nextTitle
        nextSlug
        prevTitle
        prevSlug
        slug
        date
      }
    }
  }
`;
```

### Create the list of authors page

We need to create the author page. Inside `gatsby-node.js`, let's drop in the template for authors page, and create the page. We don't need to pass in anything during page creation, we can later on query the all authors information within the page.

```diff
// gatsby-node.js

exports.createPages = ({ graphql, actions }) => {
  const { createPage } = actions;

  return new Promise((resolve, reject) => {
    const postPage = path.resolve("src/templates/post.jsx");
    const tagPage = path.resolve("src/templates/tag.jsx");
    const categoryPage = path.resolve("src/templates/category.jsx");
+   const authorsPage = path.resolve("src/templates/Authors/index.jsx");
    resolve(
      graphql(
        `
          {
            allMarkdownRemark {
              edges {
                node {
                  frontmatter {
                    tags
                    category
                  }
                  fields {
                    slug
                  }
                }
              }
            }
          }
        `
      ).then(result => {
        // ... other page creations

+       createPage({
+         path: `/authors/`,
+         component: authorsPage
+       });

      })
    );
  });
}
```

And inside this page, we can query all authors using `allAuthorYaml`:

```jsx
// src/templates/Authors/index.jsx

import React from 'react'

export default ({
  data: {
    allAuthorYaml: { edges: authorNodes },
  },
}) => (
  <div>
    {authorNodes.map(({ node: author }, index) => (
      <div key={`author-${author.id}`}>{author.id}</div>
    ))}
  </div>
)

export const pageQuery = graphql`
  query AuthorsQuery {
    allAuthorYaml {
      edges {
        node {
          id
          bio
          twitter
        }
      }
    }
  }
`
```

### Create the author pages

An author’s page displays the author details and lists all the posts by that author. So our page query will include two parts, one queries the author information, and the other queries the posts.

Let me outline the few things we need to do first:

- Add a field `authorId` to each post during `onCreateNode`, inside `gatsby-node.js` \* _see notes below why we need this_
- Create pages for each author, also implemented at `gatsby-node.js` during `createPages`. And we pass the author's id in context to be later consumed to query current author's posts.
- In author's page, query:
  - author's details
  - all posts from this author

\*There is a hiccup, [currently we cannot filter posts with mapped schema](https://github.com/gatsbyjs/gatsby/issues/7251#issuecomment-429651084). So the normal filter that looks like

```js
allMarkdownRemark(
  filter: {
    fields: { author: { id: { eq: $authorId } } }
  }) {
    edges {}
}
```

will not work.

I’m sure there are works around. What I eventually came up with was to create a field `authorId` for each post, and filter with that created field. I'm not very sophisticated with GraphQL so if there is a standard or better way to do this, please let me know.

_Create `authorId` field to post nodes_

```js
// gatsby-node.js

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

  if (Object.prototype.hasOwnProperty.call(node.frontmatter, 'author')) {
    createNodeField({
      node,
      name: 'authorId',
      value: node.frontmatter.author,
    })
  }
}
```

_Query `authorId` in the `createPages` query_

Once we've created this field, it will show up on the `field` schema of our queries. We can also use this field to feed in to the context for author's pages that will later be consumed to query author's posts:

```diff
// gatsby-node.js

// the query for createPages:
graphql(
  `
    {
      allMarkdownRemark {
        edges {
          node {
            frontmatter {
              tags
              category
            }
            fields {
              slug
+             authorId
            }
          }
        }
      }
    }
  `
```

_Create author pages_

Now we can create authors' pages

```js
// gatsby-node.js

// resolves from the query from 👆
const authorSet = new Set();
result.data.allMarkdownRemark.edges.forEach(edge => {
  if (edge.node.fields.authorId) {
    authorSet.add(edge.node.fields.authorId);
  }
}

// create author's pages inside export.createPages:
const authorList = Array.from(authorSet);
authorList.forEach(author => {
  createPage({
    path: `/author/${_.kebabCase(author)}/`,
    component: authorPage,
    context: {
      authorId: author
    }
  });
});
```

_Component implementation_

Finally, we can implement the front end component that:

- queries author details as well as filtered posts by this author
- renders the data

```jsx
// templates/Author/index.jsx
import React from 'react'

export default ({
  data: {
    authorYaml: { id, bio, twitter },
    allMarkdownRemark: { edges: postNodes },
  },
}) => (
  <div>
    <div>
      <h2>{id}</h2>
      <a href={`https://twitter.com/${twitter}/`} target="_blank">
        {`@${twitter}`}
      </a>
      <p>
        <em>{bio}</em>
      </p>
    </div>
    <hr />
    <p>{`Posted by ${id}: `}</p>
    {postNodes.map(({ node: post }, idx) => (
      <div key={post.id}>
        <a href={post.fields.slug}>{post.frontmatter.title}</a>
      </div>
    ))}
  </div>
)

export const pageQuery = graphql`
  query PostsByAuthorId($authorId: String!) {
    allMarkdownRemark(filter: { fields: { authorId: { eq: $authorId } } }) {
      edges {
        node {
          id
          frontmatter {
            title
            author {
              id
            }
          }
          fields {
            authorId
            slug
          }
        }
      }
    }
    authorYaml(id: { eq: $authorId }) {
      id
      bio
      twitter
    }
  }
`
```

If you'd like, you may check out this feature at this [CodeSandbox](https://codesandbox.io/embed/github/wgao19/gatsby-curious-community/tree/multi-author-blog/?fontsize=14).

{% codesandbox 32624281w6 %}

## Till next time 🤞

To wrap up, we can map node types by specifying `mapping` in `gatsby-config.js`. This API is designed to fit generic use cases that involve mapping certain node type to another. And it is flexible in terms of types of files it accepts for the mapping, as well as how the mapping is defined.

However, mapped node types are not supported in query filtering, yet 😭 Hack this around by duplicating a field for `authorId` that we later consume for filtering.

Once again, check out this [gatsby-starter-multiple-author](https://github.com/wgao19/gatsby-starter-multiple-author), [demo on Netlify](https://gatsby-starter-multiple-author.netlify.com/) 😁.

![](https://thepracticaldev.s3.amazonaws.com/i/a8jrpywlgqsr2vs4feg0.png)

## References

- Documentation missing installation step, [#13134](https://github.com/gatsbyjs/gatsby/issues/13134)
- Add `mapping` documentation, [#4007](https://github.com/gatsbyjs/gatsby/issues/4007)
- How to query all posts by an author when you have multiple authors per post, [#7251](https://github.com/gatsbyjs/gatsby/issues/7251)
- [Gatsby Config documentation on mapping](https://www.gatsbyjs.org/docs/gatsby-config/#mapping-node-types)
- [Gatsby Node API](https://www.gatsbyjs.org/docs/actions/#createPage) on `createPage`
- [`gatsby-transformer-yaml`](https://www.gatsbyjs.org/packages/gatsby-transformer-yaml/?=transformer-yaml)
- Gatsby Curious Community [CodeSandbox for the multi-author feature only](https://codesandbox.io/embed/github/wgao19/gatsby-curious-community/tree/multi-author-blog/?fontsize=14), [GitHub](https://github.com/wgao19/gatsby-curious-community)
- [Handle multiple authors in Jekyll](https://www.codingpedia.org/ama/how-to-handle-multiple-authors-in-jekyll/)
- [Pandoc title block multiple author support](https://pandoc.org/MANUAL.html#extension-pandoc_title_block)
- [YAML](https://en.wikipedia.org/wiki/YAML)