GraphQL

  Written by The Jahia Team
 
Developers
   Estimated reading time:

1 Introduction

GraphQL provides an alternative to a REST / CRUD model that allows more flexibility in the query, manipulation of the contents and services called. It provides a full framework, with cache capabilities and extensions, all fully integrated within DX.
Please consult http://graphql.org/learn/ to understand GraphQL concepts.

2 GraphQL integration in DX

2.1 OSGi bundles

DX comes with several GraphQL OSGi bundles.

2.1.1 Graphql-java

https://github.com/Jahia/graphql-java

This is the Java implementation of GraphQL specifications

2.1.2 Graphql-java-servlet

https://github.com/Jahia/graphql-java-servlet
This bundle provides the following GraphQL endpoint:

/{context}/modules/graphql

2.1.3 Graphql-java.annotations:

https://github.com/Jahia/graphql-java-annotations
This bundle provides annotations for GraphQL integration.

2.2 DX module

2.2.1 Graphql-core:

https://github.com/Jahia/graphql-core/tree/master/graphql-dxm-provider

3 GraphQL JCR Implementation

We provide a jcr implementation that allows to perform queries and mutations against the repository. This implementation adds support to Relay (http://facebook.github.io/relay/docs/en/introduction-to-relay.html)

3.1 JCR Query

3.1.1 Workspace

All queries are prefixed by

jcr (workspace: LIVE|EDIT) {
}

workspace is an optional parameter that specifies the workspace on which the query will be executed. If not set, the EDIT (default) workspace is used.

3.1.2 Session

The session (logged user) used for queries is the JCR Session from the current HTTP request. If you not logged in into DX, a guest user is used for queries (in such case queries in EDIT workspace will bring errors, as guest user is not allowed to read content in that workspace).

3.1.3 JCRQuery

We provide various fields/methods to query the JCR:

  • nodeById
  • nodeByPath
  •  nodesById
  • nodesByPath
  • nodesByQuery
  • nodeTypeByName
  • nodeTypes

Each field can have its own parameters or can define filters.
Using GraphiQL you can get the documentation regarding the parameters and returned values of each of these fields.

3.1.4 Internationalization

I18n is supported, content in a specific language can be found using the language parameter on displayName, properties and property fields.
Example:

{
  jcr(workspace: LIVE) {
    nodeByPath(path: "/sites/digitall/home") {
      displayName(language: "en")
    }
  }
}

3.1.5 Permissions

As we are using the JCR session from the http session, permissions are natively supported.

3.1.6 Sample queries

{
  jcr(workspace: LIVE) {
    nodeByPath(path: "/sites/digitall/home") {
      children(typesFilter: {types: ["jnt:page"]}) {
        nodes {
          name
          name_en: displayName(language: "en")
          name_fr: displayName(language: "fr")
          createdBy: property(name: "jcr:createdBy") {
            value
          }
          descendants(limit: 2, typesFilter: {types: ["jmix:list"]}) {
            nodes {
              path
            }
          }
          ancestors(upToPath: "/sites") {
            path
          }
        }
      }
    }
  }
}

{
  jcr(workspace: LIVE) {
    nodesByQuery(query: "select * from [jnt:page]", offset: 2, limit: 10) {
      edges {
        index
        cursor
        node {
          displayName(language: "en")
        }
      }
    }
  }
}

{
  jcr(workspace: LIVE) {
    nodeByPath(path: "/sites/digitall/home") {
      children(typesFilter: {types: ["jnt:page"]}, propertiesFilter: {filters: [{property: "j:templateName", value: "home"}]}) {
        nodes {
          name
          name_en: displayName(language: "en")
          name_fr: displayName(language: "fr")
          createdBy: property(name: "jcr:createdBy") {
            value
          }
          template: property(name: "j:templateName") {
            value
          }
        }
      }
    }
  }
}

3.2 JCR Mutations

3.2.1 Workspace

All mutations are prefixed by

jcr (workspace: LIVE|EDIT) {
}

workspace is an optional parameter that specifies the workspace on which the mutation will be executed. If not set, the EDIT (default) workspace is used.

3.2.2 Session

Like for the queries, the session (logged user) used for mutations is the JCR Session from the current HTTP request. If you are not logged in into DX, a guest user is used for mutations (in such case most of the mutations will bring errors, as guest user is not allowed to modify the JCR contents).

JCR session saves are done automatically at the end of each "jcr" field. So you can do multiple operations inside the same "jcr" field, and only one session save will be performed at the end. By doing so you are sure that nothing is persisted in case something went wrong inside your "jcr" block. If you need multiple saves, you can do multiple "jcr" blocks in the same mutation.

3.2.3 JCRMutations

We provide various fields/methods to modify the JCR repository:

  • addNode
  • addNodesBatch
  • deleteNode
  • markNodeForDeletion
  • unmarkNodeForDeletion

And also: 

  • mutateNode | mutateNodes | mutateNodesByQuery
    • addChild
    • addChildrenBatch
    • addMixins
    • delete
    • markForDeletion
    • move
    • removeMixins
    • rename
    • reorderChildren
    • setPropertiesBatch
    • unmarkForDeletion
    • publish
    • startWorkflow
    • addVanityUrl
    • mutateChildren (allow the same previous mutation fields on children)
    • mutateDescendant | mutateDescendants (allow the same previous mutation fields on descendant)
    • mutateVanityUrl | mutateVanityUrls
      • delete
      • move
      • update
    • mutateProperty | mutateProperties
      • addValue
      • addValues
      • delete
      • removeValue
      • removeValues
      • setValue
      • setValues
  • mutateVanityUrls
    • delete
    • move
    • update

Each field can have its own parameters or can define filters.
Using GraphiQL you can get the documentation regarding the parameters and returned values of each of these mutation fields.

3.2.5 Permissions

As we are using the JCR session from the http session, permissions are natively supported.

3.2.6 Modified nodes

If you want to retrieve all the nodes that have been modified by a mutation you can use the field: modifiedNodes available inside the jcr field.

3.2.7 Sample mutations

{
  mutation {
    jcr(workspace: EDIT) {
      mutateNode(pathOrId: "/sites/mySite/home") {
        delete
      }
    }
  }
}


{
  mutation {
    jcr(workspace: EDIT) {
      addNode(parentPathOrId: "/sites/mySite", name: "page", properties: [
        {language: "en", name: "jcr:title", type: STRING, value: "Page"}, 
        {name: "j:templateName", type: STRING, value: "2col"}], primaryNodeType: "jnt:page") {
        	uuid
      },
      modifiedNodes {
        uuid,
        name
      }
    }
  }
}

{
  mutation {
    jcr(workspace: EDIT) {
      mutateNode(pathOrId: "/sites/mySite/page") {
        mutateProperty(name: "j:templateName") {
          setValue(type: STRING, value: "3col")
        }
      }
    }
  }
}

{
  mutation {
    jcr(workspace: EDIT) {
      mutateNode(pathOrId: "/sites/mySite/page") {
        addMixins(mixins: "jmix:tagged")
      }
    }
  }
}

{
  mutation {
    jcr(workspace: EDIT) {
      mutateNode(pathOrId: "/sites/mySite/page") {
        publish
      }
    }
  }
}

3.3 GraphiQL

GraphiQL is a graphical tool that helps building GraphQL queries or mutations.
The tool is available here: http://{hostname}:{port}/{context}/modules/graphql-dxm-provider/tools/graphiql.jsp

4 GraphQL in custom modules

4.1 GraphQL in views

A simple way to use GraphQL in DX views is to call the GraphQL endpoint with an ajax request.
You can first use GraphiQL to build your query and then use it in your view:

<script>
    var graphQLQuery =  '{  jcr {   nodesByPath(paths: "/sites/") {    children {     nodes {      path      name     }    }   }  } } '
    var xhr = new XMLHttpRequest();
    xhr.responseType = 'json';
    xhr.open("POST", "/modules/graphql");
    xhr.setRequestHeader("Content-Type", "application/json");
    xhr.setRequestHeader("Accept", "application/json");
    xhr.onload = function () {
        console.log('data returned:', xhr.response);
    }
    xhr.send(JSON.stringify({query: graphQLQuery}));
</script>
Note that you can use any framework (jQuery, etc ..) to simplify this code.

For integration with modern js frameworks (react, angular, etc ..) you can use Apollo JS client:
https://github.com/apollographql/apollo-client

4.2 GraphQL extensions

It is possible for a DX module to extend the GraphQL grammar, the same way as we did for the JCR implementation.
Example:
https://github.com/Jahia/graphql-core/tree/master/graphql-extension-example

5 GraphQL configuration

The configuration file contains the matching permission for a field type (Class / method). A module can provide its own configuration for the field types it provides.
A default configuration file is available here:

/digital-factory-data/karaf/etc/org.jahia.modules.graphql.provider-default.cfg

To build your own configuration, add the configuration file in your project, prefix its name by: org.jahia.modules.graphql.provider- and edit the pom.xml of your module to reference it.
For example:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>build-helper-maven-plugin</artifactId>
    <executions>
        <execution>
            <id>attach-artifacts</id>
            <phase>package</phase>
            <goals>
                <goal>attach-artifact</goal>
            </goals>
            <configuration>
                <artifacts>
                    <artifact>
                        <file>
                            src/main/resources/META-INF/configurations/org.jahia.modules.graphql.provider-custom.cfg
                        </file>
                        <type>cfg</type>
                        <classifier>graphql-cfg</classifier>
                    </artifact>
                </artifacts>
            </configuration>
        </execution>
    </executions>
</plugin>

A full example is available here:
https://github.com/Jahia/graphql-core/blob/master/graphql-dxm-provider/pom.xml