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.
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>
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:
- Create a
graphqlTypes
folder in one of your light modules: - Create a file
content-type-name.yaml
and define the content type as following:
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.
Query the GraphQL API
Fetch a content type schema.
{
__type(name: "job") {
name
fields {
name
type {
name
kind
ofType {
name
kind
}
}
}
}
}
Fetch a single record thanks to its id.
{
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
{
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
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
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.
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:
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.