Customizing the schema
Contents
This section describes how to customize your schema deployment
Integrating Guillotine to an existing schema
You may have an existing GraphQL schema and wish to integrate Guillotine to this schema. In that case, use the functions createContext
and createHeadlessCmsType
instead of createSchema
Example
var guillotineLib = require('/lib/guillotine');
var graphQlLib = require('/lib/graphql');
var schema = createSchema();
exports.post = function (req) {
var body = JSON.parse(req.body);
var result = graphQlLib.execute(schema, body.query, body.variables);
return {
contentType: 'application/json',
body: JSON.stringify(result)
};
};
function createSchema() {
var context = guillotineLib.createContext(); (1)
return context.schemaGenerator.createSchema({
query: createRootQueryType(context),
dictionary: context.dictionary (2)
});
}
function createRootQueryType(context) {
return context.schemaGenerator.createObjectType({
name: 'Query',
fields: {
guillotine: {
type: guillotineLib.createHeadlessCmsType(context), (3)
resolve: function () {
return {};
}
}
}
});
}
1 | Creates the context necessary to create Guillotine types. |
2 | Passes the dictionary to the schema creation. The use of a dictionary is necessary to define interface implementations. |
3 | Creates the Guillotine Headless CMS type |
Extending the schema
The default Guillotine schema provides fields based on the content type schemas. But you might want to add/modify/delete fields (new retrieval methods, search in a separate database, virtual fields, …).
The guillotine schema is entirely configurable. When creating a schema, you can define a listener for a GraphQL type. This listener will be called before the type is created and given the possibility to modify the type creation parameters.
GraphQL - Enonic XP Library
A library has been implemented to facilitate the creation of a GraphQL service on Enonic XP: |
Example
In this example, we have the types Author and Post generated. But we wish to apply the following modifications:
-
Author should have a new field "fullName" that is the concatenation of firstName and lastName
-
Author data field "email" should require admin rights to be retrieved.
-
Author data field "birthDate" should not be accessible through the GraphQL API.
-
Author should have a new field "posts" returning all the blog posts written by an author
var contentLib = require('/lib/xp/content');
var guillotineLib = require('/lib/guillotine');
var graphQlLib = require('/lib/graphql');
var schema = guillotineLib.createSchema({
creationCallbacks: {
'com_enonic_app_myapp_Author_Data': function(context, params){ (1)
params.fields.fullName = { (2)
type: graphQlLib.GraphQLString,
resolve: function (env) {
return env.source.firstName + ' ' + env.source.lastName;
}
};
params.fields.email.resolve = function (env) { (3)
return authLib.hasRole('system.admin') ? env.source.email : null
};
delete params.fields.birthDate; (4)
},
'com_enonic_app_myapp_Author': function(context, params){ (1)
params.fields.posts = { (5)
type: graphQlLib.list(graphQlLib.reference('com_enonic_app_myapp_Post')),
resolve: function (env) {
return contentLib.query({
contentTypes: [app.name + ":Post"],
filters: {
hasValue: {
field: "data.author",
values: [env.source._id]
}
}
}).hits;
}
};
}
}
);
exports.post = function (req) {
var body = JSON.parse(req.body);
var result = graphQlLib.execute(schema, body.query, body.variables);
return {
contentType: 'application/json',
body: JSON.stringify(result)
};
};
1 | Passes a callback that will be called before the creation of the specified GraphQL type. It receives the Guillotine context and the object type creation parameters. Starting from version interface types are also customizable. |
2 | Adds a new string field "fullName" concatenating two other fields. The resolution function will query contents of type post having the current author ID as field "data.author" |
3 | Overwrites the resolution function of an existing field "email" |
4 | Deletes an existing field "birthDate" |
5 | Adds a new field "posts" returning a list of posts. |
These are only examples. You could also modify the type Query
and add an entire new API next to the Headless CMS API.
Starting from Guillotine provides two new methods to simplify work with subscriptions and execution of a GraphQL query:
-
createWebSocketData(req)
- create data object for websocket from request
webSocket: {
data: guillotineLib.createWebSocketData(req),
subProtocols: ['graphql-ws']
}
-
execute(params)
- This method allows you to execute a GraphQL query. To execute this method, it’s enough to passquery
andvariables
. In this case, a schema will be created for each branch of the site and can be further customised using theschemaOptions
parameter. This schema will be automatically updated when the application is added to the site or when it is removed and re-deployed. Schemas created outside of this method have to be updated manually.
List of properties for params
object:
Name | Description | Default value |
---|---|---|
query:String |
GraphQL query. Property is required. |
|
variables:Object |
Variables for GraphQL query. Property is optional. |
|
siteId:String |
Site ID. Property is optional. |
ID of a current site. |
branch:String |
Branch. Property is optional. |
Branch from request. |
schema:SCHEMA |
GraphQL SCHEMA. Property is optional. |
|
schemaOptions:Object |
SchemaOptions object to customize schema. Property is optional. |
|
context:Object |
GraphQL context. Accessible in resolve function via |
List of properties for schemaOptions
object:
Name | Description |
---|---|
applications: String or Array.<String> |
Allowed application keys in addition to this site. Property is optional. |
allowPaths:String or Array.<String> |
Allowed content paths in addition to this site. Property is optional. |
subscriptionEventTypes:String or Array.<String> |
Specifies event type patterns to be listened by GraphQL Subscription. Property is optional. |
const guillotineLib = require('/lib/guillotine');
exports.post = function (req) {
let input = JSON.parse(req.body);
let params = {
query: input.query,
variables: input.variables
};
return {
contentType: 'application/json',
body: guillotineLib.execute(params)
};
};
or
const guillotineLib = require('/lib/guillotine');
const contentLib = require('/lib/xp/content');
const contextLib = require('/lib/xp/context');
const portalLib = require('/lib/xp/portal');
exports.post = function (req) {
let siteConfig = contextLib.run({
branch: req.branch
}, () => contentLib.getSiteConfig({
key: portalLib.getSite()._id,
applicationKey: 'com.enonic.app.guillotine'
}));
let input = JSON.parse(req.body);
let params = {
query: input.query,
variables: input.variables,
schemaOptions: {
applications: siteConfig.applications,
allowPaths: siteConfig.allowPaths,
subscriptionEventTypes: siteConfig.subscriptionEventTypes
}
};
return {
contentType: 'application/json',
body: guillotineLib.execute(params)
};
};
or
const guillotineLib = require('/lib/guillotine');
const SCHEMA = guillotineLib.createSchema();
exports.post = function (req) {
let input = JSON.parse(req.body);
let params = {
query: input.query,
variables: input.variables,
schema: SCHEMA
};
return {
contentType: 'application/json',
body: guillotineLib.execute(params)
};
};