Auto generating CMS based on your GraphQL schema.

Sarkis Arutiunian
9 min readNov 1, 2016

--

second version of GraphQL CMS

I was really excited about capabilities graphQL gives you when I first started to work with it.

So far this pretty new but powerful tool doesn’t have that good support from developers community as I would like it to have.

In my last article I wrote about npm package mongoose-schema-to-graphql which will help any developers who works with Mongoose to short their code twice and save the same amount of time.

Today I will talk about my new package graphql-auto-generating-cms which probably could save you even more time, and let you think more about architecture and business processes then about building UI for admin-panel. You will find some screenshot on the end of the article.

As you already figured out from the name, this package will use your printed schema to automatically generate fully-functional CMS without any additional efforts.The most important is that without any changes in your app architecture and current code!

Package contains two modules: first is a simple React component you will use to render CMS, second is Node/Express middleware to provide schema and additional configurations if you needed.

Basically you have two ways of how to generate CMS: first way is with minimum efforts, you would just provide schema and insert CMS component wherever you want, in a separate rout or inside another component “wrapper”. For example to provide footer or header with login system or just to extend CMS functionality. And second way is more detailed and advanced configuration. In both situation you have to follow the simple pattern we will talk about shortly.

Right now this package doesn’t support list and nested elements, but it will be provided in future releases. For now you can solve this problem just by extending autogenerated CMS. Will talk how to do that in this article.

What does CMS support:

  • Pagination
  • Create new items
  • Update
  • Remove
  • Exclude some types from CMS
  • Detailed rules to generate CMS
  • Field validation based on your schema
  • Custom pages to extend CMS functionality

What it doesn’t support [will be supported in future releases]:

  • Nested elements and lists [could be solved by using custom pages]
  • Routing [just not first priority]
  • File uploading

So far functionality provided by this module will be enough for most cases of projects, and will save you a good amount of time and money.

It’s good enough out of the box for some static content pages, and for any other solution you can just extend functionality by providing custom CMS pages.

Based on the the above lets separate this article to following paragraphs:

  • Common requirements
  • Prepare to start
  • Fast method of generating CMS
  • More detail configured CMS generation
  • Config file structure
  • Extend CMS with your additional functionalities

Common requirements

Actually this is pretty simple. All your Query methods has to have: {offset: Int!, limit: Int!} arguments which basically will be used for pagination, because without pagination if you would provide 60k documents to CMS component it will crushed.

In your resolver it could look like the following:

return new Promise((resolve, reject) => {
Ingredients.find(query).skip(offset).limit(limit).exec((err, res) => err ? reject(err) : resolve(res));
});

Second requirements is in your Query method. You have to support “id” or “_id” argument with type “string” or “Int” based on which kind of storage you use. This argument will be used to query one item, so make sure that you provide this fields in graphql Type’s fields as well.

Also, if you use “findOne” instead of “find” or any other method to return one item, make sure that you wrapped it inside array, [{item}].

Keep in mind that CMS will use only graphql types which has one Query method and at least one Mutation method.

All types can only have one Query method [find] to query list of items or one item by providing id, and three Mutation method [create, update, remove].

Prepare to start

npm i -S graphql-auto-generating-cms

On your server side, we have to insert middleware to provide schema, and endpoint url which we will use on client, in example it is ‘/graphql_cms_endpoint’:


import express from 'express';
import graphqlCMS from 'graphql-auto-generating-cms/lib/middleware';
import schema from '../schema';
const printSchema = require('graphql/utilities/schemaPrinter').printSchema;
let app = express();

let config = {schema: printSchema(schema)}
app.use('/graphql_cms_endpoint', graphqlCMS(config));

app.listen(port)

After that we can use our React component to render CMS wherever we want. In the example below we will run it on a separate rout ‘/graphql-cms’:

...
import GraphqlCMS from 'graphql-auto-generating-cms';

export default (
<Router onUpdate={() => window.scrollTo(0, 0)} history={browserHistory}>
<Route
path='/graphql-cms'
endpoint='/graphql_cms_endpoint'
graphql='/graphql'
components={GraphqlCMS}
/>

</Router>

Or as child component:

<GraphqlCMS
endpoint='/graphql_cms_endpoint'
graphql='/graphql'
/>

In property “endpoint” we provide same endpoint URL we provided in middleware and in “graphql” property we provide URL to our GraphQL API.

And basically that’s it! Based on which approach (simple or detailed) you will use on path ‘/graphql-cms’ you will see your ready to use autogenerated CMS. Now let’s talk about aproach how to make all this work.

Fast method of generating CMS

To use this approach and leave all work to module you have to use following naming pattern in your graphql schema. Keep in mind by using second approach you don’t have to follow any pattern so you don’t have to change anything in your existing code! I prefer second approach but this one will be very useful for new project or in dev mode.

Your CMS structure will use the same structure you used in your schema, so if you have:

{
productType: {},
userType: {},
categoryType: {},
...
}

In your side menu of CMS it will have same order:

productType
userType
categoryType

It would be the same about properties. If you have following shape:

let productType = new GraphQLObjectType({
name: 'productType',
fields: {
_id: {type: GraphQLString},
title: {type: GraphQLString},
shortDescription: {type: GraphQLString},
price: {type: GraphQLString},
isPublished: {type: GraphQLBoolean},
createdAt: {type: GraphQLString},
updatedAt: {type: GraphQLString},
bulletPoints: {type: GraphQLString},
scienceShort: {type: GraphQLString},
scienceFull: {type: GraphQLString},
}
});

In your UI of product item view page, fields will have exact same order.

So if you want to change the order in your CMS, you just have to make changes in your schema.

By the way, in the second approach with configuration, to sort the fields in CMS we will use same approach except instead of making any change in schema we will specify it in config object.

About naming. As we talk above about Query method [find] and Mutation methods [create, update, remove], In order to let module know which method is what, you have to use following pattern of naming Query`s or Mutation`s methods:

[graphql Type name]_[action]

examples:

productType_find
productType_create
productType_update
productType_remove

The pattern is pretty easy and again in the second approach we won’t have to follow this pattern or do any changes in existing code. So let’s talk about it.

More detail configured CMS generation

Pros in compare with the first method:

  • You don’t have to change your current code at all
  • You don’t have to use naming pattern
  • You can deprecate some method you don’t want to use in CMS
  • You can change the order for the side menu or fields in the view without any change in current schema
  • You can provide different side menu and fields label. By default it will use your graphql Type name for side menu and props name for fields label in the view page
  • Disabling some fields in item view page
  • You can customize which fields will be used in the table list page for each column. By default it will use [id/_id] for first column and second field of graphql Type as the second column
  • You can specify custom input types and controllers, for example “date” or ‘textarea’

By default all fields of graphql Type’s which is not mentioned in Mutation method as arguments will be shows as disabled, so you can’t edit it, it can be different fields based on your action. When you edit or create new item, each action will use arguments from its Mutation method to mark some fields disabled or to enable all of them.

So let’s see how it works.

On your server side we just have to extend config object. All config properties besides “schema” are not required.

let config = {schema: printSchema(schema)}
app.use('/graphql_cms_endpoint', graphqlCMS(config));

Config file structure

let config = {
schema: printSchema(schema),
// your printed schema [required]

exclude: ['paymentType', 'invoiceType'],
//graphql Types which you don't want to show in CMS

rules: {
// rules object is a tree with rules for each or some
// graphql types
// CMS in addition will use the same order for side menu
// and fields
// in the view page as you will provide in “rules” object

categoryType: {
// graphql Type name, by default will be used as name
// for side menu

label: 'Categories',
// custom side menu name

listHeader: {
// data from this fields will be used to show on first
// column [id] and second [title] on list page.
// you can provide couple of fields for each columns
// so it will shows as “String” + “ “ + “String”

id: ['id'],
title: ['description']
},

resolvers: {
// if you don't want to use the naming pattern
// you have to provide
// Query's and Mutation's method name for each
// graphql Type.

find: {
resolver: 'getCategories'
// Query method name
},
create: {
resolver: 'addCategory'
// Mutation method name

allowed: true
},
update: {
resolver: 'updateCategory'
// Mutation method name

allowed: true
},
remove: {
allowed: false
// if you don't want to provide some method
// to client
// side you can depreciate it for any action
// besides “find”
}
},
fields: {
_id: {},
// never exclude this field or “id” you always
// have to provide id

sortNumber: {
label: 'custom field name to show in UI',
inputControl: 'input',
// can be “input” or “textarea”

inputType: 'number',
// can be any input type: date, text, file etc.

disabled: true,
// will disable field from editing

exclude: false,
// if true (by default false) it won't
// provide this field
// to client side so you can't see it in UI
},
name: {},
// you can also provide empty object
// if you want to just order fields

createdAt: {},
updatedAt: {},
isPublished: {}
}
}
}
}

Extend CMS with your additional functionalities

You can also improve and extend CMS functionality by providing custom pages to CMS with your own logic. For example to handle some features auto-generator doesn’t support yet, like lists or file uploading.

I’m not going to teach you on how to build React components, so I will show you the logic of how to provide custom components to CMS.

All custom pages will have first priority in side menu and will show above other menu points.

On your client side we have to provide one more property “newMenuItems” to component:

<Route
path='/graphql-cms'
endpoint='/graphql_cms_endpoint'
graphql='/graphql'
newMenuItems={customPages}
components={GraphqlCMS}
/>

And in that property we provide array with new menu points “customPages” has following structure:

let customPages = [
{
label: 'Custom Page',
secret: 'uniqeForEachComponentSecret',
view: {
secret: 'sameUniqeComponentSecret',
component: CustomDashboard //your custom React component
}
}
]

Above code will look like screenshot below:

I hope this package will be helpful for you. If you like this project, you can star/fork it on GitHub to be up to date about new releases.

This is the first release so if you find any bugs please let me know.

Also it will be very appreciated if you can help with any of the following:

  • Well written documentation
  • Your participation and contribution to this project to keep it alive
  • Any suggestion on what would you like to see in future releases

Thank you.

Currently I’m working on new version of module, so Star project on GitHub to be up to date.

What will new version support:

  • Nested properties in graphQL Type, any depth
  • graphQLList support
  • Routing
  • File uploading

So with new release auto-generated CMS will handle 90% of your needs from CMS, just by providing printed GraphQL schema!

example code

GitHub

Screenshots:

--

--