Page tree
Skip to end of metadata
Go to start of metadata

A new GraphQL module has been released as part of DX Core.

Refer to GraphQL module in the main product documentation for up-to-date information. This page and the incubator module are no longer maintained.

That modules provides an implementation of GraphQL in Magnolia based on the GraphQL java library. The module only covers the query feature of GraphQL, the mutation will be available in a further release.

JIRAN/A
Gitgraphql

Installation

Maven is the easiest way to install the module. Add the following dependency to your bundle:

<dependency>
	<groupId>info.magnolia.graphql</groupId>
	<artifactId>magnolia-graphql-integration</artifactId>
	<version>${graphQLVersion}</version>
</dependency>

Versions

1.0Magnolia 6.2

Usage

Once installed and configured, the system will deploy a GraphQL endpoint.

Configuration

The module will deploy automatically the following configuration items:

  • A GraphQL servlet endpoint mapped on /.graphql
  • The MIME mapping corresponding to GraphQL
  • A user role graphql-anonymous allowing the anonymous user to access the /.graphql endpoint over HTTP GET

Do not forget to give anonymous access to the different JCR workspaces you will query.

Define a GraphQL content type

To define a GraphQL content type:

  1. Create a graphqlTypes folder in one of your light modules:
  2. Create a file content-type-name.yaml and define the content type as following:
job.yaml
description: "Job"
workspace: shop-jobs
nodeType: shop-job
objectType:
  $type: contentType
  contentTypeName: job
query:
  job:
    $type: getByIdOrPath
    description: "Get the job thanks to its id or its path"
  jobs:
    $type: list
    description: "Get the list of jobs"


The module comes with two query fields out of the box:

  • getByIdOrPath will expose a query field (job in the example above) with two arguments id and path returning a single record thanks to its id or path.
  • list will expose a query field (jobs in the example above) returning the list of records defined in the workspace


All the fields configured as a date in the content type will have a parameter dateFormat assigned allowing to control the returned date format.

The system will automatically register the submodels as GraphQL types. However if your model contain references to other content types, you will have to configure a GraphQL type for each of them.

Query the GraphQL API

Fetch a content type schema.

Raw HTTP body request
{
  __type(name: "job") {
    name
    fields {
      name
      type {
        name
        kind
        ofType {
          name
          kind
        }
      }
    }
  }
}

Fetch a single record thanks to its id.

Raw HTTP body request
{
  job(id: "405348ed-4bdb-4c0c-ba01-66c1588e450b") {
	title
    description
    startDate(dateFormat: "yyyy-MM-dd HH:mm")
    contact {
    	email
    	phoneNumber
    	addresses {
    		street
    		city
    		country
    	}
    }
  }
}

Fetch a list of record

Raw HTTP body request
{
  jobs {
	title
    description
    startDate(dateFormat: "yyyy-MM-dd HH:mm")
    contact {
    	email
    	phoneNumber
    	addresses {
    		street
    		city
    		country
    	}
    }
  }
}

Customisation

Define your own query fields

Create a query field generator definition

SearchJobsQueryFieldGeneratorDefinition.java
package info.magnolia.graphql.sample.schema.query;

import info.magnolia.graphql.core.schema.query.ConfiguredQueryFieldGeneratorDefinition;
import info.magnolia.graphql.core.schema.query.QueryFieldType;
import info.magnolia.graphql.sample.execution.datafetcher.SearchJobsDataFetcher;

@QueryFieldType("searchJobs")
public class SearchJobsQueryFieldGeneratorDefinition extends ConfiguredQueryFieldGeneratorDefinition {

    public SearchJobsQueryFieldGeneratorDefinition() {
        this.setGeneratorClass(SearchJobsQueryFieldGenerator.class);
        this.setDataFetcherClass(SearchJobsDataFetcher.class);
    }
}

Implement the query field generator

SearchJobsQueryFieldGenerator.java
package info.magnolia.graphql.sample.schema.query;

import static graphql.schema.GraphQLArgument.newArgument;
import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition;

import info.magnolia.graphql.core.factory.MgnlGraphQLFactory;
import info.magnolia.graphql.core.schema.GraphQLSchemaBuilder;
import info.magnolia.graphql.core.schema.builder.ContentTypeBuilderContext;
import info.magnolia.graphql.core.schema.query.AbstractQueryFieldGenerator;
import info.magnolia.graphql.core.schema.query.ConfiguredQueryFieldGeneratorDefinition;

import javax.inject.Inject;

import graphql.Scalars;
import graphql.schema.GraphQLFieldDefinition;
import graphql.schema.GraphQLList;
import graphql.schema.GraphQLTypeReference;

public class SearchJobsQueryFieldGenerator extends AbstractQueryFieldGenerator<ConfiguredQueryFieldGeneratorDefinition> {

    @Inject
    public SearchJobsQueryFieldGenerator(ConfiguredQueryFieldGeneratorDefinition definition,
                                          ContentTypeBuilderContext context, MgnlGraphQLFactory mgnlGraphQLFactory,
                                          GraphQLSchemaBuilder schemaBuilder) {
        super(definition, context, mgnlGraphQLFactory, schemaBuilder);
    }

    @Override
    protected GraphQLFieldDefinition delegateGenerate() {
        return newFieldDefinition()
                .name(this.getDefinition().getName())
                .description(this.getDefinition().getDescription())
                .type(GraphQLList.list(
                        GraphQLTypeReference.typeRef(this.getContext().getContentTypeBuilderDefinition().getName()))
                )
                .argument(newArgument().name("city").type(Scalars.GraphQLString).build())
                .argument(newArgument().name("country").type(Scalars.GraphQLString).build())

                .build();
    }
}

Set the right data fetcher.

SearchJobsDataFetcher.java
package info.magnolia.graphql.sample.execution.datafetcher;

import info.magnolia.cms.util.QueryUtil;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import javax.jcr.Node;

import com.machinezoo.noexception.Exceptions;

import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;

public class SearchJobsDataFetcher implements DataFetcher<List<Node>> {
    /** Query statement. */
    public static final String QUERY = "SELECT * FROM [%s]";

    @Override
    public List<Node> get(DataFetchingEnvironment environment) throws Exception {
        List<Node> nodes = new ArrayList<>();
        StringBuilder queryString = new StringBuilder(QUERY);
        List<String> whereClauses = new ArrayList<>();

        // City
        String city = environment.getArgument("city");
        if (city!=null) {
            whereClauses.add("lower(city) like '" + city.toLowerCase() + "%'");
        }

        // Country
        String country = environment.getArgument("country");
        if (country!=null) {
            whereClauses.add("country = '" + country.toUpperCase() + "'");
        }

        if (whereClauses.size()>0) {
            queryString
                    .append(" WHERE ")
                    .append(whereClauses.stream().collect(Collectors.joining(" AND ")));
        }

        QueryUtil.search("jobs", String.format(queryString.toString(), "job"))
			.forEachRemaining(node -> nodes.add(Node.class.cast(node)));

        return nodes;
    }
}


Once done, you can configure a query field as following:

job.yaml
description: "Job"
workspace: shop-jobs
nodeType: shop-job
objectType:
  $type: contentType
  contentTypeName: job
query:
  job:
    $type: getByIdOrPath
    description: "Get the job thanks to its id or its path"
  jobs:
    $type: list
    description: "Get the list of jobs"
  jobsSearch:
    $type: searchJobs
    description: "Search for jobs"


Warnings

  • This module is at INCUBATOR level.

Changelog

  • Version 1.0 - Initial release of the module.