Embedding
Contents
Guillotine is also available as a standalone library which may be embedded into your custom Enonic application. This gives you extensive control over the API.
To create a new app with the Guillotine library boot-strapped: enonic project create -r starter-guillotine |
Follow the instructions below to embed Guillotine into your existing application.
Add library
Update the dependencies of your project:
dependencies {
implementation "com.enonic.xp:portal-api:${xpVersion}"
include "com.enonic.lib:lib-guillotine:<version>"
}
Replace <version> with the actual version you want to use, see available versions on Enonic Market |
Creating an endpoint
When using the Guillotine library, you control where and how your API is exposed. Most commonly, the API is mounted in the context of a site, which means you will have to add a site.xml file in your app, and then add the application to the specific site:
Controller
Starting off, you will need a minimal JavaScript controller file that will handle the requests. Below is a minimal example:
var guillotineLib = require('/lib/guillotine'); (1)
var graphQlLib = require('/lib/graphql'); (1)
var schema = guillotineLib.createSchema(); (2)
exports.post = function (req) { (3)
var body = JSON.parse(req.body); (4)
var result = JSON.stringify(graphQlLib.execute(schema, body.query, body.variables)); (5)
return {
contentType: 'application/json',
body: JSON.stringify(result)
};
};
1 | The GraphQL library is already a dependency in Guillotine and does not need explicitly need to be added to your Gradle file |
2 | Creates the GraphQL schema the first time the service is called. |
3 | Handles POST requests |
4 | Parses the JSON body to retrieve the GraphQL query and variables |
5 | Executes the query and variables against the schema created |
Mounting
The controller alone is not enough to give you an end-point. The following approaches are available.
The various approaches are described below:
Service
Service endpoints are automatically mounted in a unique URL-pattern, contextual of your site.
- Example
Simply place the controller file above in your app using the following pattern: /src/main/resources/services/<service-name>/<service-name>.js
That’s it
Mapping
As opposed to services, mappings are explicitly declared.
- Example
This time, however, you may place the controller anywhere within your app resources structure, i.e. /src/main/resources/controllers/guillotine.js
. Then declare the mapping in your site.xml schema. For instance like this
<mappings>
<mapping controller="/controllers/graphql.js" order="50">
<pattern>/myapi</pattern>
</mapping>
</mappings>
Using the example above, the API will be available directly in context of your site like this: <url-to-site>/myapi
.
Filters
Filters are similar to mappings, but may also work in combination with other controller on the same path. I.e. a filter handling POST
requests may for instance be mapped to the site root, even if your site serves content using GET
requests on the same endpoint.
Example filter mapping
<mappings>
<mapping filter="/controllers/graphql.js" order="50">
<pattern>/</pattern>
</mapping>
</mappings>
Subscriptions and websockets
The Guillotine API also support GraphQL subscriptions. This is implemented via websockets. To enable this functionality, your controller must be extended.
Make sure you have a dependency for websockets in your application:
dependencies {
implementation "com.enonic.xp:portal-api:${xpVersion}"
include "com.enonic.xp:lib-websocket:${xpVersion}"
include "com.enonic.lib:lib-guillotine:<version>"
}
const guillotineLib = require('/lib/guillotine');
exports.get = function (req) {
if (req.webSocket) {
return {
webSocket: {
data: guillotineLib.createWebSocketData(req),
subProtocols: ['graphql-ws']
}
};
}
};
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)
};
};
exports.webSocketEvent = guillotineLib.initWebSockets();
CORS headers
To enable flexible access to the API from browser clients, you may also need to set specific CORS headers.
The example below includes both support for websockets and CORS headers:
const guillotineLib = require('/lib/guillotine');
//──────────────────────────────────────────────────────────────────────────────
// Constants
//──────────────────────────────────────────────────────────────────────────────
const CORS_HEADERS = {
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Origin': '*'
};
//──────────────────────────────────────────────────────────────────────────────
// Methods
//──────────────────────────────────────────────────────────────────────────────
exports.options = function () {
return {
contentType: 'text/plain;charset=utf-8',
headers: CORS_HEADERS
};
};
exports.get = function (req) {
if (req.webSocket) {
return {
webSocket: {
data: guillotineLib.createWebSocketData(req),
subProtocols: ['graphql-ws']
}
};
}
};
exports.post = function (req) {
let input = JSON.parse(req.body);
let params = {
query: input.query,
variables: input.variables
};
return {
contentType: 'application/json',
headers: CORS_HEADERS,
body: guillotineLib.execute(params)
};
};
exports.webSocketEvent = guillotineLib.initWebSockets();
API browser
Finally - to simplify use of your API, you may also embed a GraphQL API browser on the very same endpoint. As oppsed to the GraphQL API, which uses the POST
method, the API browser will be handling the GET
requests.
Make sure you have a dependency to GraphQL playground
dependencies {
implementation "com.enonic.xp:portal-api:${xpVersion}"
include "com.enonic.xp:lib-websocket:${xpVersion}"
include "com.enonic.lib:lib-guillotine:<version>"
include "com.enonic.lib:lib-graphql-playground:<version>"
}
const guillotineLib = require('/lib/guillotine');
const graphqlPlaygroundLib = require('/lib/graphql-playground');
//──────────────────────────────────────────────────────────────────────────────
// Constants
//──────────────────────────────────────────────────────────────────────────────
const CORS_HEADERS = {
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Origin': '*'
};
//──────────────────────────────────────────────────────────────────────────────
// Methods
//──────────────────────────────────────────────────────────────────────────────
exports.options = function () {
return {
contentType: 'text/plain;charset=utf-8',
headers: CORS_HEADERS
};
};
exports.get = function (req) {
if (req.webSocket) {
return {
webSocket: {
data: guillotineLib.createWebSocketData(req),
subProtocols: ['graphql-ws']
}
};
}
let body = graphqlPlaygroundLib.render();
return {
contentType: 'text/html; charset=utf-8',
body: body
};
};
exports.post = function (req) {
let input = JSON.parse(req.body);
let params = {
query: input.query,
variables: input.variables
};
return {
contentType: 'application/json',
headers: CORS_HEADERS,
body: guillotineLib.execute(params)
};
};
exports.webSocketEvent = guillotineLib.initWebSockets();