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, take care 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.

Enonic’s own apps 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 specified Content instance, the response might include existing Guillotine types like Content.

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 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)
    }
};

Contents

Contents