Safis CMS includes a GraphQL API that will help you read, manage and deliver your content through a flexible schema that will be dynamically updated based on the existing content types.
First of all, the access_token needs to be sent through the Authorization header in order for a query or mutation to be authorized and correctly excecuted. Otherwise, an error will be returned.
The access_token value can be found after you log in with your GitHub OAuth App (using the path set in the OAUTH_LOGIN_URL env variable) and authorize it. Once you authorized your App, you can find access_token value in your browser's cookies:
The GraphQL Playground is enabled by default and it can be accessed through the /playground path.
This is the initial schema that will be available after the CMS was started for the first time:
# indicates what kind of value a filed will take
enum ContentTypeFieldTypeEnum {
String
Number
Boolean
Reference # links to other content types that implement the ContentType type (coming soon)
}
# used to add a new field for a content type
input ContentTypeFieldInput {
id: ID!
name: String!
description: String
type: ContentTypeFieldTypeEnum!
}
# used to add a new content type
input ContentTypeInput {
id: ID
name: String!
description: String
fields: [ContentTypeFieldInput!]
}
# used to update an existing content type
input ContentTypeUpdateInput {
id: ID!
name: String
description: String
fields: [ContentTypeFieldInput!]
}
# represents a field that belongs to a content type
type ContentTypeField {
id: ID!
name: String!
description: String
type: ContentTypeFieldTypeEnum!
}
# represents a content type, content types will implement this
type ContentType {
id: ID!
name: String!
description: String
fields: [ContentTypeField!]!
}
type Query {
contentType(id: ID!): ContentType # given an id, returns a content type
contentTypes: [ContentType] # return all content types available
}
type Mutation {
addContentType(input: ContentTypeInput!): ContentType # adds a new content type
updateContentType(input: ContentTypeUpdateInput!): ContentType # updates an existing content type
deleteContentType(id: ID!): Boolean! # deletes an existing content type
}A user can add new content types based on the structure described when using the addContetType mutation, these content types are considered dynamic since they will be automatically added to the schema after they were successfully created.
Let's create a new User content type that will represent users for our awesome application, update it and see how these changes affect the schema.
mutation addUserContentType {
addContentType(input: {
id: "user",
name: "User",
description: "This represents a user for my awesome app",
fields: [{
id: "userId",
name: "Id",
description: "This is the user unique id",
type: String, #expects a value of type "ContentTypeFieldTypeEnum"
# isUnique: true | false # coming sooon...
# isRequired: true | false # coming sooon...
}, {
id: "name",
name: "Name",
# please notice that the "description" fields is optional
type: String,
}, {
id: "age",
name: "Age",
type: Number,
},
]
}) {
id
name
description
fields {
id
name
description
type
}
__typename
}
}Response
{
"data": {
"addContentType": {
"id": "user",
"name": "User",
"description": "This represents a user for my awesome app",
"fields": [
{
"id": "userId",
"name": "Id",
"description": "This is the user unique id",
"type": "String"
},
{
"id": "name",
"name": "Name",
"description": null,
"type": "String"
},
{
"id": "age",
"name": "Age",
"description": null,
"type": "Number"
}
],
"__typename": "ContentType"
}
}
}After we add, update or delete a content type, the schema should reflect the latest changes. The schema updates will include a new type (implementing the Content type) that will describe our User content type, input types for adding and updating Users.
Additionally, the schema will expose two queries, one for getting all User content entries and another for getting a User content entry matching a given id; as well as 3 new mutations for adding, updating and deleting Users.
# types
type User implements Content {
id: ID
userId: String
name: String
age: Float
sys: SysContent
}
input UserInput {
userId: String
name: String
age: Float
}
input UserUpdateInput {
id: ID!
userId: String
name: String
age: Float
}
# queries
user(id: ID!): User
users: [User]
# mutations
addUser(input: UserInput!): User
updateUser(input: UserUpdateInput!): User
deleteUser(id: ID!): BooleanNow that we added the User content type, let's run the contentTypes query and get some results back;
query getAllContentTypes {
contentTypes {
id
name
description
fields {
id
name
type
}
}
}Response
{
"data": {
"contentTypes": [
{
"id": "user",
"name": "User",
"description": "This represents a user for my awesome app",
"fields": [
{
"id": "userId",
"name": "Id",
"type": "String"
},
{
"id": "name",
"name": "Name",
"type": "String"
},
{
"id": "age",
"name": "Age",
"type": "Number"
}
]
}
]
}
}It is also possible to query for a specific content type using its id:
query getUserContentTypeInfo {
contentType(id: "user") {
id
name
description
fields {
id
name
type
}
}
}Response
{
"data": {
"contentType": {
"id": "user",
"name": "User",
"description": "This represents a user for my awesome app",
"fields": [
{
"id": "userId",
"name": "Id",
"type": "String"
},
{
"id": "name",
"name": "Name",
"type": "String"
},
{
"id": "age",
"name": "Age",
"type": "Number"
}
]
}
}
}When a content type struture needs to be changed, we can use the updateContentType to update ony what is needed. The fields that can be updated are name, description and fields (field definitions).
# updating only the description
mutation updateUserContentTypeDescription {
updateContentType(input: {
id: "user",
description: "This an updated description for the User content type"
}) {
id
name
description
__typename
fields {
id
name
description
type
__typename
}
}
}Response
{
"data": {
"updateContentType": {
"id": "user",
"name": "User",
"description": "This an updated description for the User content type",
"__typename": "ContentType",
"fields": [
{
"id": "userId",
"name": "Id",
"description": "This is the user unique id",
"type": "String",
"__typename": "ContentTypeField"
},
{
"id": "name",
"name": "Name",
"description": null,
"type": "String",
"__typename": "ContentTypeField"
},
{
"id": "age",
"name": "Age",
"description": null,
"type": "Number",
"__typename": "ContentTypeField"
}
]
}
}
}Please consider that if you need to add, updated or delete a field definition, you still need to include all the fields that will be added, updated; or exclude the field to be removed from the fields definition array.
Adding a new field
mutation addNewFieldToUserContentType {
updateContentType(input: {
id: "user",
fields: [{
id: "userId",
name: "Id",
description: "This is the user unique id",
type: String,
}, {
id: "name",
name: "Name"
type: String,
}, {
id: "age",
name: "Age",
type: Number,
}, {
id: "isActive",
name: "Is user active?",
type: Boolean,
}
]
}) {
id
name
description
__typename
fields {
id
name
description
type
__typename
}
}
}Response
{
"data": {
"updateContentType": {
"id": "user",
"name": "User",
"description": "This an updated description for the User content type",
"__typename": "ContentType",
"fields": [
{
"id": "userId",
"name": "Id",
"description": "This is the user unique id",
"type": "String",
"__typename": "ContentTypeField"
},
{
"id": "name",
"name": "Name",
"description": null,
"type": "String",
"__typename": "ContentTypeField"
},
{
"id": "age",
"name": "Age",
"description": null,
"type": "Number",
"__typename": "ContentTypeField"
},
{
"id": "isActive",
"name": "Is user active?",
"description": null,
"type": "Boolean",
"__typename": "ContentTypeField"
}
]
}
}
}Please notice that schema updates the ContentType type, and input types and after a structural change is made
type User implements Content {
id: ID
userId: String
name: String
age: Float
isActive: Boolean # new field
sys: SysContent
}
input UserInput {
userId: String
name: String
age: Float
isActive: Boolean # new field
}
input UserUpdateInput {
id: ID!
userId: String
name: String
age: Float
isActive: Boolean # new field
}Updating an existing field
mutation updateFieldOnUserContentType {
updateContentType(input: {
id: "user",
fields: [{
id: "userId",
name: "Id",
description: "This is the user unique id",
type: String,
}, {
id: "name",
name: "Name"
type: String,
}, {
id: "age",
name: "Age",
type: Number,
}, {
id: "isActive",
name: "Acive", # name changed
type: Boolean,
}
]
}) {
id
name
description
__typename
fields {
id
name
description
type
__typename
}
}
}Response
{
"data": {
"updateContentType": {
"id": "user",
"name": "User",
"description": "This an updated description for the User content type",
"__typename": "ContentType",
"fields": [
{
"id": "userId",
"name": "Id",
"description": "This is the user unique id",
"type": "String",
"__typename": "ContentTypeField"
},
{
"id": "name",
"name": "Name",
"description": null,
"type": "String",
"__typename": "ContentTypeField"
},
{
"id": "age",
"name": "Age",
"description": null,
"type": "Number",
"__typename": "ContentTypeField"
},
{
"id": "isActive",
"name": "Acive",
"description": null,
"type": "Boolean",
"__typename": "ContentTypeField"
}
]
}
}
}Deleting an existing field
mutation deleteFieldOnUserContentType {
updateContentType(input: {
id: "user",
fields: [{
id: "userId",
name: "Id",
description: "This is the user unique id",
type: String,
}, {
id: "name",
name: "Name"
type: String,
}, {
id: "age",
name: "Age",
type: Number,
}
# isActive field was removed
]
}) {
id
name
description
__typename
fields {
id
name
description
type
__typename
}
}
}Response
{
"data": {
"updateContentType": {
"id": "user",
"name": "User",
"description": "This an updated description for the User content type",
"__typename": "ContentType",
"fields": [
{
"id": "userId",
"name": "Id",
"description": "This is the user unique id",
"type": "String",
"__typename": "ContentTypeField"
},
{
"id": "name",
"name": "Name",
"description": null,
"type": "String",
"__typename": "ContentTypeField"
},
{
"id": "age",
"name": "Age",
"description": null,
"type": "Number",
"__typename": "ContentTypeField"
}
]
}
}
}Let's assume we have a Test content type, whose id is test. We can delete this content type by using the deleteContentType mutation.
mutation deleteTestContentType {
deleteContentType(id: "test")
}Response
{
"data": {
"deleteContentType": true
}
}Note: the schema will update after deleting the Test content type.
Coming soon...
Content entries can be managed by using the queries and mutations, with their types and input types, after one or more content types were defined.
Please consider the updates made to the schema after we added the User content type
# types
type User implements Content {
id: ID
userId: String
name: String
age: Float
sys: SysContent
}
input UserInput {
userId: String
name: String
age: Float
}
input UserUpdateInput {
id: ID!
userId: String
name: String
age: Float
}
# queries
user(id: ID!): User
users: [User]
# mutations
addUser(input: UserInput!): User
updateUser(input: UserUpdateInput!): User
deleteUser(id: ID!): BooleanThe CMS will create a new mutation automatically to add users after the creation of the new content type with the form add{CONTENT_TYPE}. For example, addUser for adding Users.
Additionally, an input {CONTENT_TYPE}Input type (e.g. input UserInput) will be gnerated to validate the form and the required fields of the User to be added.
Example
mutation addNewUser {
addUser(input: {
name: "Isaac",
age: 21,
userId: "customId123"
}) {
id # System generated id
userId
name
age
sys {
id
name
description
__typename
}
__typename
}
}Response
{
"data": {
"addUser": {
"id": "cc482b53-e05a-472c-8a9c-08faf70badd6",
"userId": "customId123",
"name": "Isaac",
"age": 21,
"sys": {
"id": "User",
"name": "User",
"description": "This an updated description for the User content type",
"__typename": "SysContent"
},
"__typename": "User"
}
}
}Similarly to the addNewUser mutation and the input UserInput type, we will have an updateUser mutation and an input UserUpdateInput type to define the payload of the user to be updated.
Example
mutation updateUser {
updateUser(input: {
id: "cc482b53-e05a-472c-8a9c-08faf70badd6", # System generated id
name: "Isaac Newton",
}) {
id
userId
name
age
sys {
id
name
description
__typename
}
__typename
}
}Response
{
"data": {
"updateUser": {
"id": "cc482b53-e05a-472c-8a9c-08faf70badd6",
"userId": "customId123",
"name": "Isaac Newton",
"age": 21,
"sys": {
"id": "User",
"name": "User",
"description": "This an updated description for the User content type",
"__typename": "SysContent"
},
"__typename": "User"
}
}
}You can also query for a specific content entry using the CONTENT_TYPE query (e.g. user) and passing the entry id.
Example
query getOneContentEntry {
user(id: "cc482b53-e05a-472c-8a9c-08faf70badd6") {
id
userId
name
age
sys {
id
name
description
__typename
}
__typename
}
}Response
{
"data": {
"user": {
"id": "cc482b53-e05a-472c-8a9c-08faf70badd6",
"userId": "customId123",
"name": "Isaac Newton",
"age": 21,
"sys": {
"id": "User",
"name": "User",
"description": "This an updated description for the User content type",
"__typename": "SysContent"
},
"__typename": "User"
}
}
}If you need to get all of the content entries for a given content type you can use the CONTENT_TYPEs query. For example, users.
Example
query getAllContentEntries {
users {
id
userId
name
age
sys {
id
name
description
__typename
}
__typename
}
}Response
{
"data": {
"users": [
{
"id": "cc482b53-e05a-472c-8a9c-08faf70badd6",
"userId": "customId123",
"name": "Isaac Newton",
"age": 21,
"sys": {
"id": "User",
"name": "User",
"description": "This an updated description for the User content type",
"__typename": "SysContent"
},
"__typename": "User"
},
{
"id": "faf34685-f7ba-408e-a3e4-05f0f484bc39",
"userId": "abc123",
"name": "Celia Cruz",
"age": 8,
"sys": {
"id": "User",
"name": "User",
"description": "Azúcar!",
"__typename": "SysContent"
},
"__typename": "User"
}
]
}
}Deleting a content entry is also possible with the delete{CONTENT_TYPE} mutation (e.g. deleteUser) and the entry id.
Example
mutation deleteContentEntry {
deleteUser(id: "cc482b53-e05a-472c-8a9c-08faf70badd6")
}Response
{
"data": {
"deleteUser": true
}
}Ability to extend the GraphQL schema (Coming soon...)

