Multilingual Drupal, GraphQL and Gatsby

In previous blogs I mentioned the options you have when linking Drupal to Gatsby. There are two flavours, namely JSON:API and GraphQL. At first I chose JSON:API because I didn't need multilingual support. I do now ;-)

For that exact reason I need to redesign my React components to support multiple languages. The backend, Drupal 8, wasn't that hard to change. Install GraphQL, add a language, configure tranlations and there you go. The frontend need a bit more TLC. The Drupal backend was going to use GraphQL and that requires a different approach.

Endpoint

First the endpoint. For JSON:API you need the gatsby-source-drupal plugin, where you need to set the apiBase (default: jsonapi). For GraphQL you use a different gatsby plugin, gatsby-source-drupal-graphql where you only need to tell what the domain of the Drupal backend is (and the rest is done automatically). Authentication with JSON:API can be keybased (with the key_auth module), but for GraphQL it's better to use the simple_oauth module in Drupal. With this module is easy to create an oAuth link between Drupal and Gatsby. To get the oAuth token, it's best to create a sepearte user and userrole. This way you can better manage permissions.

Queries

The two different Gatsby plugins use different schemas and also a different query base. Where you could use an "allNodePage" query in JSON:API, you need to use "nodeQuery" with GraphQL. An example of the query I use in gatsby-node.js:

{
  drupal {
    nodes: nodeQuery(
      filter: {
        conditions: [
          { operator: EQUAL, field: "status", value: "1" }
          { operator: IN, field: "type", value: ["page", "article", "portfolio", "webform"] }
        ]
      },
      limit: 1000000
    ) {
    entities {
      entityTranslations {
        entityId
        entityBundle
        entityLanguage {
          id
        }
        ... on Drupal_Node {
          path {
            alias
          }
        }
      }
    }
  }
}

With this query you can retrieve all published nodes of the type page, article, portfolio and webform. You'll also get all the translations (if present).

For listing all portfolio-items I use the following query:

{
    drupal {
        nodeQuery(
            filter: {
                conditions: [
                    {operator: EQUAL, field: "status", value: "1"}
                    {operator: EQUAL, field: "type", value: "portfolio"}
                ]
            },
            limit: 1000,
            sort: {field: "field_timeline.end_value", direction: DESC}
        ) {
            entities {
                nl: entityTranslation(language: NL) {
                    ... on Drupal_NodePortfolio {
                        ... PortfolioFields
                    }
                }
                en: entityTranslation(language: EN) {
                    ... on Drupal_NodePortfolio {
                        ... PortfolioFields
                    }
                }
            }
        }
    }
}

Fragments

With that we get to "fragments". Fragments can best be compared to JOIN queries in MySQL. With fragments you can reuse parts of a graphql query (which suits the "Don't Repeay Yourself" philosophy very well). It's not only very easy to add fields to the query, it's also essential to use when retrieving files.

A fragment is defined as follows:

export const PortfolioFields = graphql`
fragment PortfolioFields on Drupal_NodePortfolio {
    nid
    title
    body {
        summary
        processed
    }
    fieldMediaImage {
        entity {
            ... on Drupal_MediaImage {
                ...MediaImage
            }
        }
    }
}
`;

Media / images

In the aforementioned fragment you'll also see a nested fragment, to retrieve the images. With JSON:API / gatsby_source_drupal it's relatively eay to query for an image from Drupal, but GraphQL / gatsby_source_drupal_graphql won't automatically transfer the images. I solved it by adding a resolver to retrieve the files. With the fragment you can then query for the images.

gatsby-node.js:

exports.createResolvers = ({
    actions,
    getCache,
    createNodeId,
    createResolvers,
}) => {
    const { createNode } = actions
    createResolvers({
        Drupal_MediaImage: {
            gatsbyImageFile: {
                type: `File`,
                resolve(source) {
                    return createRemoteFileNode({
                        url: source.fieldMediaImage.url,
                        getCache,
                        createNode,
                        createNodeId,
                    })
                },
            },
        },
    })
}

Fragment:

export const MediaImage = graphql`
  fragment MediaImage on Drupal_MediaImage {
    fieldMediaImage {
      url
      alt
    }
    gatsbyImageFile {
      childImageSharp {
        fluid {
          originalName
          ...GatsbyImageSharpFluid
        }
      }
    }
  }
`;
back_blog