Best Practices
Contents
Recommendations from our experience of extending Guillotine
Naming conventions
When extending existing schema types with custom fields or adding new schema types, make sure to use unique names in order to avoid schema conflicts.
We recommend using prefixes:
`<prefix>_fieldName` - for fields related to your application. `<Prefix>_TypeName` - for other schema types
For fields within your custom schema types, you may safely drop the prefix.
Apps developed by Enonic never use prefixes. This is done in order to keep commonly used fields and the schema as easy to read as possible. |
Remember, GraphQL fields cannot start with a number, and may not contain -
or .
. Use _
as a separator instead.
Field placement
When adding new fields, there are several locations where you may be placing them in the schema, here we present the different options, and what to use when:
Query Type (query field)
The Query
type only has a single default field guillotine
, meaning you may extend it with other fields of your preference.
Generally, fields placed at this level should not be directly related to "content", specifically it means it should not be returning any types generated by Guillotine.
For instance, you may add a field returning the current server time like this:
-
myapp_serverTime: DateTime
- this field returns the current server time.
For example:
exports.extensions = function (graphQL) {
return {
types: {},
creationCallbacks: {
Query: function (params) {
params.addFields({
myapp_serverTime: {
type: graphQL.DateTime
}
});
}
},
resolvers: {
Query: {
myapp_serverTime: function (env) {
// return current server time
}
}
}
}
};
HeadlessCms Type (guillotine field)
The guillotine
field is the only standard root field in the Query
type. Utilizing this field enables you to access a variety of functionalities, including retrieving individual Content instances by key.
We recommend to use this rule: if field is related to Content
, then add it to the HeadlessCms
type.
-
myapp_menu(contentKey: String!): MyApp_Menu
- Returns a menu for specifiedContent
instance, the response might include existing Guillotine types likeContent
.
For example:
exports.extensions = function (graphQL) {
return {
types: {
MyApp_Menu: {
fields: {
// add fields here
}
}
},
creationCallbacks: {
HeadlessCms: function (params) {
params.addFields({
myapp_menu: {
type: graphQL.reference('MyApp_Menu'),
args: {
contentKey: {
type: graphQL.nonNull(graphQL.GraphQLString)
}
}
}
});
}
},
resolvers: {
HeadlessCms: {
myapp_menu: function (env) {
// return menu for specified Content instance
}
}
}
}
};
Other types
You may also extend any other standard or generated Guillotine type, for instance a custom content type. The recommendations here are essentially the same as for the HeadlessCms
type.
Coding guidelines
The extensions
function can get huge, so we recommend to split it into several files. For example, you can create a file for each property such as types
, creationCallbacks
, resolvers
and others. If needed, you can split them into several files too.
const typesFactory = require('./types-factory');
const creationCallbacksFactory = require('./creation-callbacks-factory');
const resolversFactory = require('./resolvers-factory');
exports.extensions = function (graphQL) {
return {
types: typesFactory.create(graphQL),
creationCallbacks: creationCallbacksFactory.create(graphQL),
resolvers: resolversFactory.create(graphQL)
}
};
Let’s take a look at the resolvers-factory.js
file:
This example below demonstrates how to split the resolvers
property into two files: headless-cms-resolvers-factory.js
and query-resolvers-factory.js
, where each file contains resolvers for fields of the corresponding type.
const headlessCmsResolversFactory = require('./headless-cms-resolvers-factory');
const queryResolversFactory = require('./query-resolvers-factory');
exports.create = function (graphQL) {
return {
Query: queryResolversFactory.create(graphQL),
HeadlessCms: headlessCmsResolversFactory.create(graphQL)
}
};