Understanding Relationships
Relationships are the connections you make between different lists of content in Keystone. What you build depends a great deal on what you need. This guide will show you how to reason about, and configure relationships in Keystone so you can bring value to your project through structured content.
How to reason about relationships
Keystone provides a lot of flexibility when it comes to relationships. To get what you need there are two key questions you need to answer:
- Which side of the relationship do I need to access data from?
Your needs here will define whether your relationship needs to be one, or two-sided.
- How many connections do need on either side of my relationship?
Understanding this will determine the kind of cardinality you need to configure.
These topics are easier to understand by example. We’ll explore them, as well as the basics of setting up relationships through the use case of a blog app that has a post
type for blog entries, and user
type for post authors. Let’s get started…
How to define a relationship in Keystone
Relationships are made using the relationship
field type within a list()
. In our blog example we can connect a blog post
to some users
using the relationship field’s ref
configuration option like so:
import { config, createSchema, list } from '@keystone-next/keystone/schema';import { text, relationship } from '@keystone-next/fields';export default config({lists: createSchema({User: list({ fields: { name: text() } }),Post: list({fields: {title: text(),content: text(),authors: relationship({ ref: 'User', many: true, }),},}),}),});
The many
config option relates to cardinality which we explore later
One-sided & two-sided relationships
In Keystone it’s possible to define relationships from one, or both sides of the two lists you’re connecting. We refer to these as one-sided, and two-sided relationships:
One-sided
Our example above is one-sided: the Post
list relates to the User
list via the authors
field. This kind of relationship will let us query for the authors
of a post in our GraphQL API like so:
Query {allPosts {titlecontentauthors {name}}}
Two-sided
If we can find all the authors
of a post, we can also find all the posts written by a particular user. To do this we need to configure a two-sided relationship in our schema:
import { config, createSchema, list } from '@keystone-next/keystone/schema';import { text, relationship } from '@keystone-next/fields';export default config({lists: createSchema({User: list({fields: {name: text(),// relates posts to authorsposts: relationship({ref: 'Post.authors', many: true }),},}),Post: list({fields: {title: text(),content: text(),// relates authors to postsauthors: relationship({ ref: 'User.posts', many: true }),},}),}),});
In the example above we added a posts
field to the User
list, and changed the ref
config of the authors
field to be User.posts
.
In a two-sided relationship the ref
config must be formatted as <listName>.<fieldName>
and both sides must refer to each other.
Now that our relationship is two-sided we can query all the posts written by each user like so:
Query {allUsers {nameposts {titlecontent}}}
Things to keep in mind:
Two-sided relationships are declared in two places, but there is only one relationship between both lists.
Both fields share 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-referencing relationships
Keystone also lets you define one, and two-sided relationships that refer to the same list. To make a one-sided Twitter style following relationship we do the following:
import { config, createSchema, list } from '@keystone-next/keystone/schema';import { text, relationship } from '@keystone-next/fields';export default config({lists: createSchema({User: list({fields: {name: text(),follows: relationship({ ref: 'User', many: true }),},}),}),});
Or change this into a two-sided relationship to also access the followers of every user:
import { config, createSchema, list } from '@keystone-next/keystone/schema';import { text, relationship } from '@keystone-next/fields';export default config({lists: createSchema({User: list({fields: {name: text(),follows: relationship({ ref: 'User.followers', many: true }),followers: relationship({ ref: 'User.follows', many: true }),},}),}),});
The only relationship configuration not currently supported is having a field reference itself, e.g. friends: relationship({ ref: 'User.friends', many: true })
.
Establishing cardinality
Cardinality is a term used to describe how many items can exist on either side of a relationship. Each side can have either one
or many
related items. Since each relationship can have one and two sides, we have the following options available:
Relationship type | One to one | One to many | Many to many |
---|---|---|---|
One-sided | ❌ | ✅ | ✅ |
Two-sided | ✅ | ✅ | ✅ |
Cardinality is defined through the relationship
field’s many
configuration option. It’s a boolean where:
false
= onetrue
= many
false
is the default state for the many config option.
Let’s explore how to set up each type of cardinality in the context of our blog:
One-sided cardinalities
One-to-many
- Posts have a single author
- Users can have multiple posts
import { config, createSchema, list } from '@keystone-next/keystone/schema';import { text, relationship } from '@keystone-next/fields';export default config({lists: createSchema({User: list({fields: {name: text(),},}),Post: list({fields: {title: text(),content: text(),author: relationship({ ref: 'User', many: false }),},}),}),});
Many-to-many
- Posts can have multiple authors
- Users can have multiple posts
export default config({lists: createSchema({User: list({fields: {name: text(),},}),Post: list({fields: {title: text(),content: text(),authors: relationship({ ref: 'User', many: true }),},}),}),});
Two-sided cardinalities
In two-sided relationships the many
option on both sides must be considered.
One-to-one
- Posts have a single author
- Users can create only one post
export default config({lists: createSchema({User: list({fields: {name: text(),post: relationship({ ref: 'Post.author', many: false }),},}),Post: list({fields: {title: text(),content: text(),author: relationship({ ref: 'User.post', many: false }),},}),}),});
One-to-many
- Posts have a single author
- Users can have multiple posts
export default config({lists: createSchema({User: list({fields: {name: text(),posts: relationship({ref: 'Post.author', many: true }),},}),Post: list({fields: {title: text(),content: text(),author: relationship({ ref: 'User.posts', many: false }),},}),}),});
Note that we’ve used many: false
in the author field and many: true
in the posts field.
Many-to-many
- Posts can have multiple authors
- Users can have multiple posts
export default config({lists: createSchema({User: list({fields: {name: text(),posts: relationship({ ref: 'Post.authors', many: true }),},}),Post: list({fields: {title: text(),content: text(),authors: relationship({ ref: 'User.posts', many: true }),},}),}),});
Note that we have used many: true
in both the authors and posts fields.
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. Their cardinality can be set using the many
flag. Keystone gives you the flexibility to choose what you want based on what you need to achieve.