Relationships
Keystone allows you to model your data as a collection of related Lists.
For example, a blogging application might have lists called Post and User, where each post has a single author.
This would be represented in Keystone by a relationship between the Post and User lists.
Defining a relationship
Relationships are implemented using the Relationship field type and defined along with other fields in createLists.
For our blog example, we could define:
keystone.createList('User', { fields: { name: { type: Text } } });
keystone.createList('Post', {
fields: {
title: { type: Text },
content: { type: Text },
author: { type: Relationship, ref: 'User', many: false },
},
});
The Relationship field type takes a config option ref which is able to reference another list in the application.
In this case, the author field will hold a reference to the User list.
If we wanted to allow a post to have multiple authors we could change our definition to
authors: { type: Relationship, ref: 'User', many: true },
We have used many: true to indicate that the post relates to multiple Users, who are the authors of that post.
The default configuration is many: false, which indicates that each post is related to exactly one user.
One-sided vs two-sided
In our example we know the authors of each post.
We can access this information from our GraphQL API by querying for the authors field of a post.
Query {
allPosts {
title
content
authors {
name
}
}
}
If we can find all authors of a post, this implies there is enough information available to find all posts written by a particular user.
To access to this information from the Users list as well, we update our list definitions as such:
keystone.createList('User', {
fields: {
name: { type: Text },
posts: { type: Relationship, ref: 'Post.authors', many: true },
},
});
keystone.createList('Post', {
fields: {
title: { type: Text },
content: { type: Text },
authors: { type: Relationship, ref: 'User.posts', many: true },
},
});
We have now added a posts field to the User list, and changed the ref config of the authors field.
We now have two Relationship fields, but importantly, we still only have one relationship.
The two fields simply represent different sides of the one relationship.
This type of configuration is called a two-sided relationship, while the original configuration without posts was a one-sided relationship.
We can now write the following query to find all the posts written by each user:
Query {
allUsers {
name
posts {
title
content
}
}
}
There are some important things to remember when defining a two-sided relationship:
- Even though there are two fields, there is only one relationship between the lists.
- The
refconfig must be formatted as<listName>.<fieldName>and both sides must refer to each other. - Both fields are sharing the same data. If you change the author of a post, that post will no longer show up in the original author's
posts.
Self-referential lists
In the above examples we defined relationships between two different lists, Users and Posts.
It is also possible to define relationships which refer to the same list.
For example if we wanted to implement a Twitter style following relationship we could define:
keystone.createList('User', {
fields: {
name: { type: Text },
follows: { type: Relationship, ref: 'User', many: true },
},
});
This one-sided relationship allows us to keep track of who each user is following. We could turn this into a two-sided relationship to also access the followers of each user:
keystone.createList('User', {
fields: {
name: { type: Text },
follows: { type: Relationship, ref: 'User.followers', many: true },
followers: { type: Relationship, ref: 'User.follows', many: true },
},
});
The only relationship configuration not currently supported is having a field reference itself, e.g. friends: { type: Relationship, ref: 'User.friends', many: true }.
Cardinality
The cardinality of a relationship is the number items which can exist on either side of the relationship.
In general, each side can have either one or many related items.
Since each relationship has two sides this means we can have one-to-one, one-to-many and many-to-many relationships.
The cardinality of your relationship is controlled by the use of the many config option.
In two-sided relationships the many option on both sides must be considered.
The follow examples will demonstrate how to set up each type of cardinality in the context of our blog.
One-sided
One-to-many
Each post has a single author, and each user can have multiple posts, however we cannot directly access a users' posts.
keystone.createList('User', {
fields: {
name: { type: Text },
},
});
keystone.createList('Post', {
fields: {
title: { type: Text },
content: { type: Text },
author: { type: Relationship, ref: 'User', many: false },
},
});
Many-to-many
Each post has multiple authors, and each user can have multiple posts, however we cannot directly access a users' posts.
keystone.createList('User', {
fields: {
name: { type: Text },
},
});
keystone.createList('Post', {
fields: {
title: { type: Text },
content: { type: Text },
authors: { type: Relationship, ref: 'User', many: true },
},
});
Twos-sided
One-to-one
Each post has a single author, and each user is only allowed to write one post.
keystone.createList('User', {
fields: {
name: { type: Text },
post: { type: Relationship, ref: 'Post.author', many: false },
},
});
keystone.createList('Post', {
fields: {
title: { type: Text },
content: { type: Text },
author: { type: Relationship, ref: 'User.post', many: false },
},
});
One-to-many
Each post has a single author, and each user can have multiple posts.
keystone.createList('User', {
fields: {
name: { type: Text },
posts: { type: Relationship, ref: 'Post.author', many: true },
},
});
keystone.createList('Post', {
fields: {
title: { type: Text },
content: { type: Text },
author: { type: Relationship, ref: 'User.posts', many: false },
},
});
Many-to-many
Each post can have multiple authors, and each user can have multiple posts.
keystone.createList('User', {
fields: {
name: { type: Text },
posts: { type: Relationship, ref: 'Post.authors', many: true },
},
});
keystone.createList('Post', {
fields: {
title: { type: Text },
content: { type: Text },
authors: { type: Relationship, ref: 'User.posts', many: true },
},
});
Summary
Keystone relationships are managed using the Relationship field type.
They can be configured as one-sided or two-sided by the ref config option, and the cardinality can be set using the many flag.
If you need help deciding which options to use, please consult the relationship configuration guide.