A Headless API example using Gatsby (Part 2)

Creating a basic Gatsby source plugin for Liferay

In the first part we basically set everything up. We created a Gatsby site and a few web content articles in Liferay.

In this part we will create the Gatsby source plugin for Liferay and use the headless API to pull in our articles.

Upfront, a lot of the steps I outline below for creating the plugin I got from the Gatsby site. Therefore, I will leave out some explanations regarding syntax and methods allowing you to read more details for yourself there. I found the Gatsby docs was very informative, allowing me to get up and running quickly.

Creating the Plugin

To begin creating our liferay source plugin, we want to first create a “plugins” directory inside our gatsby-liferay-site directory. Followed by creating another directory for our actual plugin titled “gatsby-source-liferay”.

Your path should now look something like the following:

.../gatsby-liferay-site/plugins/gatsby-source-liferay

 

Inside this directory, issue the following command to create a package.json file which will describe our plugin and any other dependencies it may need.

npm init --yes

 

Next, let’s install a few modules that we will need to make the request to our Liferay server.

npm install node-fetch query-string --save

 

Now, let’s create the main file for our source plugin. Create a file called gatsby-node.js in the .../plugins/gatsby-source-liferay directory and add the following contents.

const fetch = require("node-fetch")

const queryString = require("query-string")


exports.sourceNodes = (

  { actions, createNodeId, createContentDigest },

  configOptions

) => {

  const { createNode } = actions

  // Gatsby adds a configOption that's not needed for this plugin, delete it

  delete configOptions.plugins

  // plugin code goes here...

  console.log("Testing my plugin", configOptions)

}

 

Adding the plugin to our Gatsby site

Next we need to tell our Gatsby site about our plugin. Edit the file gatsby-config.js located in your ../gatsby-liferay-site/ directory.

We can just add our source plugin towards the top of the file. Your file should look similar to the this.

module.exports = {
  siteMetadata: {
    title: `Gatsby Default Starter`,
    description: `Kick off your next, great Gatsby project with this default starter. This barebones starter ships with the main Gatsby configuration files you might need.`,
    author: `@gatsbyjs`,
  },
  plugins: [
    {
      resolve: "gatsby-source-liferay",
      options: {
        host: "localhost:8080",
        siteId: "20123",
        authKey: "dGVzdEBsaWZlcmF5LmNvbTp0ZXN0"
      },
    },
    `gatsby-plugin-react-helmet`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/images`,
      },
    },

….

 

I went ahead and added some options we will need when we call the Liferay server. You can create the authKey by executing the following to get a Base 64 encoding.

echo -n "test@liferay.com:test" | base64

 

Now let’s check and make sure our plugin loads.

From the ../gatsby-liferay-site/ directory,  enter gatsby develop to fire up a development server.

We should see output in the console similar to the following.

success initialize cache - 0.038 s
success copy gatsby files - 0.071 s
success onPreBootstrap - 0.025 s
Testing my plugin { host: 'localhost:8080',
  siteId: '20123',
  authKey: 'dGVzdEBsaWZlcmF5LmNvbTp0ZXN0' }
warn The gatsby-source-liferay plugin has generated no Gatsby nodes. Do you need it?

 

Great, our plugin works. Hit CTRL-C and kill the development server.

Use the web service in our plugin

Let’s now move to finally using the web service to pull back our web content articles.  

Edit the gatsby-node.js file again in your ../plugins/gatsby-source-liferay directory and replace the contents with the following.

const fetch = require("node-fetch")
const queryString = require("query-string")


exports.sourceNodes = (
    {actions, createNodeId, createContentDigest},
    configOptions
) => {
    const {createNode, createNodeField} = actions

    // Gatsby adds a configOption that's not needed for this plugin, delete it
    delete configOptions.plugins

    // Helper function that processes a photo to match Gatsby's node structure
    const processContent = content => {
        const nodeId = createNodeId(`liferay-content-${content.id}`)
        const nodeContent = JSON.stringify(content)
        const nodeData = Object.assign({}, content, {
            slug: `news/${content.friendlyUrlPath}.html`,
            id: nodeId,
            parent: null,
            children: [],
            internal: {
                type: `LiferayStructuredContent`,
                mediaType: `text/html`,
                content: nodeContent,
                contentDigest: createContentDigest(content),
            },
        })
        return nodeData
    }

    // plugin code goes here...
    // console.log("Testing gatsby-source-liferay plugin", configOptions)

    // Construct Liferay API URL for structured-content
    const apiUrl = `http://${configOptions.host}/o/headless-delivery/v1.0/sites/${configOptions.siteId}/structured-contents/`

    const init = {
        method: 'GET',
        headers: {
            'Accept': 'application/json',
            'Authorization': `Basic ${configOptions.authKey}`
        }
    }


    // Gatsby expects sourceNodes to return a promise
    return (
        // Fetch a response from the apiUrl
        fetch(apiUrl, init)
        // Parse the response as JSON
            .then(response => response.json())
            // Process the JSON data into a node
            .then(data => {
                // For each query result (or 'items')
                data.items.forEach(content => {
                    console.log("Liferay article: ", content.title)

                    // Process the data to match the structure of a Gatsby node
                    const nodeData = processContent(content)

                    //console.log("nodeData--> ", nodeData);

                    // Use Gatsby's createNode helper to create a node from the node data
                    createNode(nodeData)
                })

            })
    )
}

 

Success, plugin works!

Start up your Gatsby development server again. This time the console output should display the titles of our web content articles!

success load plugins - 0.309 s

success onPreInit - 0.018 s
success initialize cache - 0.022 s
success copy gatsby files - 0.058 s
success onPreBootstrap - 0.020 s
Liferay article:  A simple article
Liferay article:  Another simple article
Liferay article:  Another simple article wo tag
success source and transform nodes - 0.291 s
success Add explicit types - 0.020 s

 

We have successfully invoked the web service and retrieved our web content articles. There are two things I will mention about the code above. First, in the processContent() method, I am creating a property “slug” for the page location and using /news/ + the friendlyUrlPath of the web content article. For example, this will give us an article that will be available at “/news/a-simple-article.html”. Second, notice we are using the Gatsby createNode() method. This is important, because it populates Gatsby’s data layer which will then let us create pages from our web content.

This means we can now query our data that we just loaded into Gatsby with GraphQL. You probably noticed GraphQL when we started up the Gatsby development server.

Open a browser and hit http://localhost:8000/___graphql

Go ahead and paste the following in the GraphiQL interface and view the results. This is a great tool to test out your queries before using them in your React components.

query MyQuery {
  allLiferayStructuredContent {
    edges {
      node {
        id
        title
        description
      }
    }
  }
}

 

Creating a template for our content

Now that our plugin is complete, it’s time to generate pages for each of our web content articles. To do this we need to create a React component that will be the template for displaying the web content articles.

Create a “templates” directory in ../gatsby-liferay-site/src/.

Then create a file called news.js inside the ../gatsby-liferay-site/src/templates/ directory and paste the following contents inside it.

import {graphql} from "gatsby";
import React from "react";
import Layout from "../components/layout"


export default class News extends React.Component{
    render() {
        const { data } = this.props;
        const { liferayStructuredContent: { contentFields, dateModified, creator: { name }, title, description } } = data;


        function createMarkup(html) {
            // console.log("createMarkup-->" + html);
            return {__html: html};
        }

        return(

            <Layout>

                <div className="news">

                    <div className="container-fluid">
                        <div className="row">
                            <div className="intro blog-intro text-center col">
                                <div className="container-fluid container-fluid-max-lg">
                                    <h1 className="h1">News</h1>
                                    <h2 className="h3">Latest updates from an external Liferay</h2>
                                </div>
                            </div>
                        </div>
                    </div>

                    <div className="container">
                        <div className="row">
                            <div className="col-md-12">
                                <article>

                                    <small>
                                        {dateModified} {name}
                                    </small>

                                    <div dangerouslySetInnerHTML={createMarkup(description) } />

                                    <h1>{title}</h1>

                                    <div dangerouslySetInnerHTML={createMarkup(contentFields[0].value.data) } />

                                </article>
                            </div>
                        </div>
                    </div>


                </div>
            </Layout>
        )
    }

}


export const pageQuery = graphql`
  query($slug: String!){
      liferayStructuredContent(slug:{eq: $slug }) {
        key
        dateModified(formatString: "MMMM DD, YYYY")
        title
        description
        slug
        creator {
          name
          familyName
          givenName
        }
        contentFields{
          value{
            data
          }
        }
      }
  }  
    
`;

 

Great, now we have our template. It is time to test everything.

We can get a listing of our pages by triggering a 404. Go ahead and enter a bogus URL for your Gatsby site. You should see something like the following.

 

All our web content articles are listed now as site pages, very cool!

Click on one of them and you will see the contents just as if it originated from the site.

 

Click back and try another. You will notice all the html styling (h1, h2, ul, etc..) created in Liferay is present. You can even use CSS class names when authoring the content in Liferay. You would just have to include the styling definitions in your Gatsby site.

Lastly, remember we tagged our web content articles as “news”. If you look back at part 1, the curl output shows tags listed as an array in the keywords field. We could add a filter to the web service to only pull articles with our tag.

For example)

curl "http://localhost:8080/o/headless-delivery/v1.0/sites/20123/structured-contents/?fields=title,keywords&filter=keywords/any(k:contains(k,'news'))" -u 'test@liferay.com:test'

 

Conclusion

What I really liked was how easy I could use Liferay to author content and use the Headless API to easily have it rendered elsewhere. This was a vastly different experience than if I had attempted to use the traditional JSON webservice to accomplish this solution. 

Great job Liferay!

I hope this is example is useful.

https://github.com/mountc/gatsby-liferay-site

Blogs

How can do the same for a portlet. I have create a portlet and depoly but cant able to fetch in gatsby