Extending
Contents
Extend, augment and customize the Guillotine API
Introduction
One of Guillotine’s super-powers is the ability to customize the GraphQL schema with your own input types, enums, unions, and interfaces, set or override data fetcher and type resolvers, and even modify existing interfaces, types and fields.
Guillotine.js
To extend the Guillotine application you need to create a file named guillotine.js
in the src/main/resources/guillotine
folder of your application.
This file must export the extensions
function with the following structure:
exports.extensions = function (graphQL) {
return {
enums: { (1)
// enum type definitions ...
},
inputTypes: { (2)
// input type definitions ...
},
interfaces: { (3)
// interfaces type definitions ...
},
unions: { (4)
// unions type definitions ...
},
types: { (5)
// output types definitions ...
},
creationCallbacks: { (6)
// creation callback definitions ...
},
resolvers: { (7)
// resolver definitions ...
},
typeResolvers: { (8)
// type resolver definitions ...
}
}
};
1 | Define custom enums. |
2 | Define custom input types. |
3 | Define custom iterfaces. |
4 | Define custom unions. |
5 | Define custom types. |
6 | Use creationCallbacks to add, remove or augment existing fields and interfaces.. |
7 | Use resolvers to set or override data fetchers for specific fields. |
8 | Use typeResolvers to set or override the type resolver for an interface or a union. |
You can omit any of the properties, but you must return an object with the same structure. Order of the properties is not important.
Usage example
Let’s imagine that we have to extend the GraphQL schema with a new type GoogleBooks
and add a new field findBooks
to the Query
type to be able to find books by query string. To execute requests to Google Books API we will use lib-http-client
library.
dependencies { include 'com.enonic.lib:lib-http-client:3.2.2' }
In our application we have to create a new file src/main/resources/guillotine/guillotine.js
and add the following content to it:
const httpClient = require('/lib/http-client');
const GOOGLE_BOOKS_API_KEY = app.config.googleBooksApiKey;
exports.extensions = function (graphQL) {
return {
types: {
GoogleBooks: {
description: 'Google Books Type',
fields: {
id: {
type: graphQL.GraphQLString,
},
title: {
type: graphQL.GraphQLString,
},
authors: {
type: graphQL.list(graphQL.GraphQLString),
},
publisher: {
type: graphQL.GraphQLString,
},
publishedDate: {
type: graphQL.GraphQLString,
},
description: {
type: graphQL.GraphQLString,
},
pageCount: {
type: graphQL.GraphQLInt,
},
language: {
type: graphQL.GraphQLString,
},
averageRating: {
type: graphQL.GraphQLFloat,
},
}
},
},
creationCallbacks: {
Query: function (params) {
params.addFields({
findBooks: {
type: graphQL.list(graphQL.reference('GoogleBooks')),
args: {
queryString: graphQL.GraphQLString,
}
}
});
},
},
resolvers: {
Query: {
findBooks: function (env) {
const response = sendRequestToBooksApi(env.args.queryString);
return response.items.map(function (item) {
const volumeInfo = item.volumeInfo;
return {
id: item.id,
title: volumeInfo.title,
authors: volumeInfo.authors,
publisher: volumeInfo.publisher,
publishedDate: volumeInfo.publishedDate,
description: volumeInfo.description,
pageCount: volumeInfo.pageCount,
language: volumeInfo.language,
averageRating: volumeInfo.averageRating,
}
});
}
}
},
}
};
function sendRequestToBooksApi(queryString) {
const response = httpClient.request({
url: 'https://www.googleapis.com/books/v1/volumes',
method: 'GET',
contentType: 'application/json',
queryParams: {
q: queryString,
key: GOOGLE_BOOKS_API_KEY,
}
});
return JSON.parse(response.body);
}
This example is very simple and does not cover all possible cases. For example, it does not handle errors from the Google Books API, does not cache values, etc. But it shows how to extend the GraphQL schema with a new type and a new field.
You can separate definitions of types, creationCallbacks, resolvers and the rest of options into different files and import them into the guillotine.js
file, to make your code more readable and maintainable.
Arguments
When Guillotine invokes the extensions function, it will pass a utility object as an argument, giving your extension acccess to standard scalars, types, type modifiers and functions:
- Scalars and Types
-
GraphQLString
,GraphQLInt
,GraphQLID
,GraphQLBoolean
,GraphQLFloat
,Json
,DateTime
,Date
,LocalTime
LocalDateTime
andreference
type. - Type modifiers
-
The
list
andnonNull
type modifiers allow applies additional validation of those values. - Functions
-
createDataFetcherResult
- allows to return object withdata
which will be as a source for children fields and provide alocalContext
to share unmodifiable data available in a child field usingenv.localContext
.
Lifecycle
The extensions
function will automatically be invoked by Guillotine when your application (the app containing the guillotine.js
controller) is started, or when Guillotine itself is started/restarted.