GatsbyでAirtableからデータを読む

2021-04-10

Gatsbyのプロジェクトで、Airtableからデータを読む方法のメモです。

親切なAirtableのヘルプ

  • Airtableでワークスペースを開き、そこから次のように[API doucumentation]を選択すると、このワークスペースでレコード(テーブルの行)をリストしたり、レコードを作成、更新、削除するための具体的なコードまで示されていて大変親切です。
AirtableのAPIのヘルプ

例えば、次のようなコードを書きます。

const IndexPage = () => {
  const Airtable = require('airtable');
  const base = new Airtable({ apiKey: `${process.env.AIRTABLE_API_KEY}` }).base(
    `${process.env.AIRTABLE_BASE_ID}`
  );

  base('Members directory')
    .select({
      maxRecords: 100,
      view: 'Key members',
    })
    .eachPage(
      (records, fetchNextPage) => {
        records.forEach((record) => {
          console.log('Name: ', record.get('Name'), ' Title: ', record.get('Title'), record.get('Photo'));
        });
        fetchNextPage();
      },
      (err) => {
        if (err) {
          console.error(err);
          return;
        }
      }
    );

なお、環境変数を使用できるようにするには、gatsby-config.js に次を追加します。

require('dotenv').config({
  path: `.env.${process.env.NODE_ENV}`,
});

すると、コンソールには次のような結果が得られました。

Airtableからのデータ取得結果
  • しかし、Gatsby では、ローカルのファイル(マークダウン等)や様々なデータ源について、GraphQL のクエリですべて持ってこれるので、Airtable からのデータ取得もそのクエリで取得できるとスマートかと思います。

gatsby-source-airtable のインストール

gatsby-source-airtableAirtable のテーブルより Gatsby アプリケーションにデータを取り込むためのプラグインです。次でインストールします。

$ npm install gatsby-source-airtable
$ npm audit fix // 必要な場合

gatsby-config.jsplugins の設定の一部を次のようにします。

plugins: [
    {
      resolve: `gatsby-source-airtable`,
      options: {
        apiKey: `${process.env.AIRTABLE_API_KEY}`,
        concurrency: 5,
        tables: [ // 複数指定可
          {
            baseId: `${process.env.AIRTABLE_BASE_ID}`,
            tableName: `Members directory`,
            // tableView: `YOUR_TABLE_VIEW_NAME`, // optional
            // queryName: `OPTIONAL_NAME_TO_IDENTIFY_TABLE`, // optional
            // mapping: { `CASE_SENSITIVE_COLUMN_NAME`: `VALUE_FORMAT` }, // optional
            // tableLinks: [`CASE`, `SENSITIVE`, `COLUMN`, `NAMES`], // optional
            separateNodeType: false, 
            separateMapType: false,
          },
        ]
      }
    },
:

この状態で gatsby develop して開発サーバーが起動した状態で、http://localhost:8000/___graphql にアクセスし、GraphiQL のクエリを次のようにしてみます。

query MyQuery {
  allAirtable(sort: {fields: data___Order, order: ASC}) {
    nodes {
      data {
        Name
        Title
        Photo {
          id
          url
        }
      }
    }
  }
}

ここで sort を指定しているのは、Airtable のレコードの並び順に出力するためです。もっと良い方法があるかもしれませんが、ここでは Airtable Order を設定し、その値の昇順にソートするようにしました。

Airtable への Order フィールドの設定

これにより GraphQL でクエリを実行すると次になります。

GraphiQL による Airtable へのアクセス

この方法では画像のURLが得られましたが、これを親コンポーネントの props で受け取り、gatsby-plugin-imageStaticImage を用いることはできません(詳細はこちら)。画像を Airtableなど CMS から読み込み場合は、GraphQL を介して画像を読み込み、GatsbyImage コンポーネントを使用する必要があります。

AirtableのAttachmentフィールドをGraphQLで読めるようにする方法

Attachementフィールドタイプ

この方法は、gatsby-source-airtableこちらに記載しています。

まず、gatsby-config.js に次のように mapping の設定をします。この例では、Photo とあるのが、Airtable Attachment フィールドタイプの field 名になります。

require('dotenv').config({
  path: `.env.${process.env.NODE_ENV}`,
});

module.exports = { 
plugins: [
    {
      resolve: `gatsby-source-airtable`,
      options: {
        apiKey: process.env.AIRTABLE_API_KEY,
        concurrency: 5,
        tables: [ // 複数指定可
          {
            baseId: process.env.AIRTABLE_BASE_ID,
            tableName: process.env.AIRTABLE_TABLE_NAME,
            mapping: { Photo: `fileNode` }, // optional
            separateNodeType: false, 
            separateMapType: false,
          },
        ]
      }
    },

この設定にしておくと次のように gatsbyImageData をクエリできるようになります。

const TeamMembers = () => {
  const data = useStaticQuery(graphql`
    query {
      allAirtable(sort: { order: ASC, fields: data___Order }) {
        nodes {
          data {
            Name
            Bio
            Homepage_URL
            Twitter_username
            GitHub_username
            Instagram_username
            YouTube_username
            Photo {
              localFiles {
                childImageSharp {
                  gatsbyImageData(
                    width: 200
                    layout: FIXED
                    placeholder: BLURRED
                    formats: [AUTO, WEBP, AVIF]
                  )
                }
              }
            }
          }
        }
      }
    }
  `);

データのコンポーネントへの設定

今回は既にあるプロジェクトの都合で次のようにデータを teamMembers という配列に入れているのですが、AirtableAttachmentフィールドタイプの Photo には複数の写真が入るので、先頭の写真だけを格納しています。

import React from 'react';
import { useStaticQuery, graphql } from 'gatsby';
import styled from 'styled-components';
import TeamMember from './TeamMember';

const TeamMembersWrapper = styled.div`
  color: purple;
`;

const TeamMembers = () => {
  const data = useStaticQuery(graphql`
    query {
      allAirtable(sort: { order: ASC, fields: data___Order }) {
        nodes {

          // 省略。上と同じです。
      }
    }
  `);

  // GraphQLで取得したAirtableのデータを元のスターターの形式のオブジェクト配列に設定
  const teamMembers = [];
  data.allAirtable.nodes.forEach((node) => {
    teamMembers.push({
      file: node.data.Photo.localFiles[0],
      header: node.data.Name,
      subheader: node.data.Bio,
      social: {
        homepage: node.data.Homepage_URL,
        twitter: node.data.Twitter_username,
        github: node.data.GitHub_username,
        instagram: node.data.Instagram_username,
        youtube: node.data.YouTube_username,
      },
    })
  });

  return (
    <TeamMembersWrapper>
      {teamMembers.map((member) => (
        <TeamMember member={member} key={member.header} />
      ))}
    </TeamMembersWrapper>
  );
};

export default TeamMembers;

データの表示

この実験用コードでは次のように表示します。

表示結果

コードは次のようになっています。

import React from 'react';
import styled from 'styled-components';
import { GatsbyImage, getImage } from 'gatsby-plugin-image';

const TeamMemberWrapper = styled.div`
  margin: 2em;
  padding: 1em;
  border: 1px solid black;
`;

const TeamMember = ({ member }) => {
  return (
    <TeamMemberWrapper>
      <GatsbyImage image={getImage(member.file)} alt={member.header} />
      <h1>{member.header}</h1>
      <p>{member.subheader}</p>
      {member.social.homepage && <p>ホームページ: {member.social.homepage}</p>}
      {member.social.twitter && <p>Twitter: {member.social.twitter}</p>}
      {member.social.github && <p>GitHub: {member.social.github}</p>}
      {member.social.instagram && <p>Instagram: {member.social.instagram}</p>}
      {member.social.youtube && <p>YouTube: {member.social.youtube}</p>}
    </TeamMemberWrapper>
  );
};

export default TeamMember;

ここでは、ヘルパー関数の getImage を使って、次のように短く書くようにしています。

<GatsbyImage image={member.file.childImageSharp.gatsbyImageData} alt={member.header} />
↓
<GatsbyImage image={getImage(member.file)} alt={member.header} />

ソースコード全体はこちらになります。