Headless React and GraphQL app with Jahia DX tutorial

  Written by The Jahia Team
 
Developers
   Estimated reading time:

This tutorial shows you how to create a Javascript application from scratch using React and GraphQL with Jahia DX as a headless content backend. The application  displays a news list that is managed by DX’s headless content management UI: Content and Media Manager.

The tutorial shows how to create a standalone application that could be deployed in any website. If you’re interested in building a Javascript application hosted in a DX module and deployed on a DX server, we recommend you use a different starting point. We have provided a starter module project on Github at the following URL: https://github.com/Jahia/dx-react-starter

Jahia Days video

This tutorial is inspired by a live coding session that was presented at Jahia Days 2018. You can see the recording of this session here: https://www.youtube.com/watch?v=6fbkH-EIkcs

Requirements

You will need the following tools to use the tutorial:

  • NodeJS
  • NPM 5.3+
  • Yarn
  • Jahia DX 7.3 with the Content and Media Manager module installed

Technologies used

This tutorial uses the following technologies:

Creating and launching the app

You use NPM’s NPX binary package runner to create a skeleton React application by executing the following commands on the command line:

  • npx create-react-app my-app
  • cd my-app
  • yarn start

You will then see the URL that you can connect to using your browser to view the compiled application. You can then start performing changes that will be immediately visible in your browser, offering fast compile-deploy cycles. 

Note that if some updates don’t work properly, just stop the server and restart it using yarn start. That should fix most issues.

Creating content using Content and Media Manager

To have content to browse and display in your Javascript application, create and publish sample content from Content and Media Manager.

To create and publish sample content:

  1. Open Content and Media Manager.
  2. Click Browser Folder and then click + Create>New content folder. Create a new folder named "my-app".
  3. In the new folder, click + Create>New content. To create a news entry, select Content:Structured>News Entry.
  4. Enter or copy content from the jahia.com website at https://www.jahia.com/about-us/news-events/press-room.
  5. Publish the new folder.

Learn more about Content and Media Manager in the Academy.

Building queries with GraphiQL

Next, use GraphiQL to get familiar with DX GraphQL queries before integrating them in your app. This example shows how to execute queries on the nodeByPath query field.

To build queries with GraphiQL:

  1. Open GraphiQL by navigating to the DX tools at http://localhost:8080/tools and then clicking on GraphiQL at the lower left.
    Note that you can use the Documentation Explorer to get familiar with the DX GraphQL API. Open the explorer by clicking on Docs in the upper-right corner.
  2. Execute the following query on the nodeByPath query field:
    {
      jcr(workspace:LIVE) {
        nodeByPath(path:"/sites/digitall/contents/my-app") {
          name
        }
      }
    }
    If you get a login exception, make sure you are properly logged in to the CMS first.
  3. Next, add children nodes to the query:
    {
      jcr(workspace:LIVE) {
        nodeByPath(path:"/sites/digitall/contents/my-app") {
          name
          children {
            nodes {
              properties {
                name
                value
              }
            }
          }
        }
      }
    }
  4. Note that the i18n properties are not returned unless you specify a language to retrieve them in. For example, modify the query to look like this:
    {
      jcr(workspace:LIVE) {
        nodeByPath(path:"/sites/digitall/contents/my-app") {
          name
          children {
            nodes {
              properties(language: "en") {
                name
                value
              }
            }
          }
        }
      }
    }
  5. Notice that the query generates too much data. Modify the query to only retrieve the properties that you need:
    {
      jcr(workspace:LIVE) {
        nodeByPath(path:"/sites/digitall/contents/my-app") {
          name
          children {
            nodes {
              properties(names: ["jcr:title", "desc"], language: "en") {
                name
                value
              }
            }
          }
        }
      }
    }

Setting up authorization

By default, DX’s REST and GraphQL API are closed even if nodes have public read permissions. The APIs are closed for security reasons. To open up the API and make it usable for your application, you must configure DX’s security filter module to allow public access. You can find more information about the security filter module here: https://github.com/Jahia/security-filter

To make the data publicly accessible:

  1. Add the org.jahia.modules.api.permissions-myapp.cfg DX configuration file to the digital-factory-data/karaf/etc folder with the following contents:
    permission.myapp.api=graphql
    permission.myapp.scope=myapp
    permission.myapp.nodeType=jnt:news,jnt:contentFolder,rep:root
    permission.myapp.pathPattern=/,/sites/[^/]+/contents/.*,/sites/[^/]+/files/.*
    The scope setup here requires creating a JWT token when we integrate authorization in the Apollo Client in the Javascript code.
  2. You also must create a org.jahia.modules.graphql.provider-myapp.cfg file for the CORS authorization with the following content:
    http.cors.allow-origin=http://localhost:3000
  3. Verify that the APIs are open by logging out and executing the GraphiQL request again.

React Material

React Material is used to style the application’s look and feel using Google’s Material design components. This library provides an out-of-the-box React component library that makes building applications with a consistent look and feel much easier and faster.

To add React Material to the project:

  1. Execute the following commands on the command line at the root of the project:
    yarn add @material-ui/core
    yarn add @material-ui/icons
  2. Add the following line to the public/index.html file:
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500">
  3. Update the media viewport property to make it compatible with mobile devices:
    <meta
      name="viewport"
      content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no"
    />
  4. To add an AppBar in App.js, first add the imports at the top of the file:
    import AppBar from '@material-ui/core/AppBar';
    import Toolbar from '@material-ui/core/Toolbar';
    import Typography from '@material-ui/core/Typography';
    import CssBaseline from '@material-ui/core/CssBaseline';
  5. Then replace the following section that was generated by the create-react-app tool:
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <h1 className="App-title">Welcome to React</h1>
      </header>
      <p className="App-intro">
        To get started, edit <code>src/App.js</code> and save to reload.
      </p>
    </div>
  6. With:
    <React.Fragment>
        <CssBaseline />
        <AppBar position="static" color="default">
            <Toolbar>
                <Typography variant="title" color="inherit">
                    Latest News
                </Typography>
            </Toolbar>
        </AppBar>
    </React.Fragment>

The AppBar displays at the top of the page with the Latest News title to prepare the layout for our NewsList component.

Connecting to GraphQL using Apollo Client

Next, add the Apollo GraphQL client library to be able to connect to DX’s GraphQL API.

To add the Apollo GraphQL client library:

  1. From the root of the project, execute the following commands on the command line:
    yarn add react-apollo
    yarn add apollo-cache-inmemory
    yarn add apollo-client
    yarn add apollo-client-preset
    yarn add apollo-link-rest
    yarn add graphql
    
  2. In Jahia DX, navigate to tools at http://localhost:8080/tools. Then, select Jahia API security config and filter : jwtConfiguration and create a new JWT Token using the following settings:
    Scopes : myapp
    Referrer : (empty)
    IPs : (empty)
  3. Click Save and copy the generated token. The example JWT_DX_TOKEN value is used in the script below.
  4. In index.js file add:
    import {ApolloProvider} from 'react-apollo';
    import {ApolloClient} from 'apollo-client';
    import {HttpLink} from 'apollo-link-http';
    import {InMemoryCache} from 'apollo-cache-inmemory';
    
    const JWTDXToken = 'JWT_DX_TOKEN';
    const httpLink = new HttpLink({
        uri: 'http://localhost:8080/modules/graphql',
        headers: {
            'Authorization': `Bearer ${JWTDXToken}`
        }
    });
    
    const client = new ApolloClient({
        link: httpLink,
        cache: new InMemoryCache()
    });
    
  5. Then modify the render call to look like this:
    ReactDOM.render(
        <ApolloProvider client={client}>
            <App />
        </ApolloProvider>
        , document.getElementById('root'));

NewsList component

Next, create a NewsList component to retrieve the list of news objects from the DX server.

To create the NewsList component:

  1. Create a NewsList.jsx file with the following content:
    import React, {Component} from 'react';
    import gql from "graphql-tag";
    import {Query} from "react-apollo";
    import GridList from '@material-ui/core/GridList';
    import GridListTile from '@material-ui/core/GridListTile';
    import Card from '@material-ui/core/Card';
    import CardContent from '@material-ui/core/CardContent';
    import Typography from '@material-ui/core/Typography';
    
    const GQL_QUERY = gql`
        query NewsListQuery($language: String) {
            jcr(workspace:LIVE) {
                nodeByPath(path:"/sites/digitall/contents/my-app") {
                    name
                    children {
                        nodes {
                            uuid
                            properties(names: ["jcr:title", "desc"], language: $language) {
                                name
                                value
                            }
                        }
                    }
                }
            }
        }
    `;
    
    class NewsList extends Component {
        render() {
            const options = {
                variables : {
                    language: "en"
                }
            };
    
            return (<Query query={GQL_QUERY} variables={options.variables}>
                {({loading, error, data}) => {
                    if (loading) return "Loading ...";
    
                    console.log(“Error:”, error);
                    console.log(“Data:”, data);
                    const cardItems = data.jcr.nodeByPath.children.nodes.map(node => (
                        <GridListTile key={node.uuid}>
                            <Card>
                                <Typography component="h1" variant="display1">{node.properties[0].value}</Typography>
                                <CardContent>
                                    <div dangerouslySetInnerHTML={{__html:node.properties[1].value}} />
                                </CardContent>
                            </Card>
                        </GridListTile>
                    ));
                    return <GridList cellHeight={350} cols={3} spacing={32}>
                        {cardItems}
                    </GridList>
                }}
            </Query>);
    
        }
    }
    
    export default NewsList;
    Note: Ensure that you save the file.
  2. In the App.js file, add the import for the new NewsList component at the top of the file:
    import NewsList from './NewsList';
  3. Then replace:
        </AppBar>
    </React.Fragment>
  4. With:
        </AppBar>
        <NewsList/>
    </React.Fragment>

Improving property retrieval

Next, improve the property retrieval by using GraphQL field aliases.

To improve property retrieval:

  1. Replace:
    properties(names: ["jcr:title", "desc"], language: $language) {
        name
        value
    }
  2. With:
    title : property(name : "jcr:title", language : $language) { value }
    body : property(name : "desc", language : $language) { value }
  3. Then, in the render replace:
    <Typography component="h1" variant="display1">{node.properties[0].value}</Typography>
    <CardContent>
        <div dangerouslySetInnerHTML={{__html:node.properties[1].value}} />
    </CardContent>
  4. With:
    <Typography component="h1" variant="display1">{node.title.value}</Typography>
    <CardContent>
        <div dangerouslySetInnerHTML={{__html:node.body.value}} />
    </CardContent>

This is safer and less prone to break over time.

You now have a base Javascript application that retrieves content from Jahia DX using GraphQL and that uses React and React Material to display the content. 

Where to go from here

Learn more about: