GraphQL many-to-many self-referential relations
This tutorial assumes that you have successfully completed the Dashboard quick start tutorial, and that you still have the Fauna Dashboard open in a browser tab/window. Create a new database before you start the tutorial. If your Dashboard session has expired:
|
To form bi-directional relations in GraphQL requires using the
@relation
directive, which is
explained in the GraphQL
relations.
In this tutorial, we explore how to form many-to-many
relations from a GraphQL Type to itself. We are using a generic Person
Type
as an example of modeling parent/child relations. That is, every Person
document can have multiple parent and multiple children relations, where a
parent and child are also of the same Person
Type. The same can be applied to
any use case where many-to-many self-referential relation holds, such as where
User
can have any number of Followers
(child relation) and each
Follower
is also a User
that can follow multiple other Users
(parent relation).
To create such self-referential relations, the relations in GraphQL are modeled using an intermediate "link table" Type.
Tutorial
Let’s first define a simple Person
Type, and attempt to make a relation to
itself.
-
Create a new schema file
Create the file
schema-self_ref.graphql
with the following content: -
Import the GraphQL schema
-
Click the IMPORT SCHEMA button. If you do not see the button, click the REPLACE SCHEMA button.
-
In the file dialog, locate and select the file
schema-self_ref.graphql
, then click the file dialog’s Open button.
An error message appears:
Many to many self-references are not allowed
The problem is that the GraphQL API does not support applying the dynamically-generated document ID to a document field as a single step, which is required to make a self-referential relation.
To solve this problem, the schema needs to be adjusted to introduce a Type dedicated to managing the relation.
-
-
Modify the schema to create a
PersonLink
Type to establish a self-referencing many-to-many relationCreate the file
schema-self-many-many-relations.gql
with the following content (or download it here): -
-
Click the REPLACE SCHEMA button.
-
In the file dialog, locate and select the file
schema-self_ref.graphql
, then click the file dialog’s Open button.
This time, the schema import should be successful.
When the schema is imported, the Fauna GraphQL API creates collections named
Person
andPersonLink
. It specifies that aPerson
document can have multiple parent documents and multiple child documents. -
-
Create Person and it’s parent-child relations
Now that you have the new schema in place, to create relationships between the parent/child persons, you have to create the "links".
-
Copy the following GraphQL mutation, which creates a
Person
document namedPerson1
, plus its children documentsChild1
,Child2
, andChild3
and its parentsParent1
andParent2
: -
Click the "new tab"
+
button in the GraphQL Playground screen in your browser (at the top left, just right of the last query tab). -
Paste the query into the left panel, and click the "Play" button.
The query should execute and it should create a list of
PersonLink
documents, each one creating aPerson
as a child and aPerson
as a parent. The response should appear in the panel on the right:{ "data": { "createPerson": { "_id": "335277188298310144", "name": "Person1", "children": { "data": [ { "_id": "335277188302504448", "child": { "_id": "335277188301455872", "name": "Child1" } }, { "_id": "335277188302506496", "child": { "_id": "335277188302505472", "name": "Child2" } }, { "_id": "335277188303554048", "child": { "_id": "335277188303553024", "name": "Child3" } } ] }, "parents": { "data": [ { "_id": "335277188304602624", "parent": { "_id": "335277188304601600", "name": "Parent1" } }, { "_id": "335277188305651200", "parent": { "_id": "335277188305650176", "name": "Parent2" } } ] } } } }
Alternately, you can create
Person
documents and then create a link to connect parent and child. For example, execute the following GraphQL queries in sequence, as you have executed the previous queries:{ "data": { "createPerson": { "_id": "332505815169630400", "name": "Child1" } } }
For the next query, copy the
_id
value forParent1
and replace the<$PARENT_ID>
string with that value, and copy the_id
forChild1
and replace the<$CHILD_ID>
string with that value.{ "data": { "createPersonLink": { "_id": "335277379989537280", "parent": { "_id": "335277379887825408", "name": "Person1" }, "child": { "_id": "335277379939205632", "name": "Child1" } } } }
-
-
To ensure that the same link is not made more than once, manually create a unique compound index on the joining fields. This index is not used directly, but as a unique index, it prevents duplicate entries from being created.
ObjectV(ref: RefV(id = "unique_personLink_parent_child", collection = RefV(id = "indexes")),ts: LongV(1651094261520000),active: BooleanV(True),serialized: BooleanV(True),unique: BooleanV(True),name: StringV(unique_personLink_parent_child),source: RefV(id = "PersonLink", collection = RefV(id = "collections")),terms: Arr(ObjectV(field: Arr(StringV(data), StringV(parent))), ObjectV(field: Arr(StringV(data), StringV(child)))), partitions: LongV(1))
map[active:true name:unique_personLink_parent_child partitions:1 ref:{unique_personLink_parent_child 0x14000194240 0x14000194240 <nil>} serialized:true source:{PersonLink 0x14000194330 0x14000194330 <nil>} terms:[map[field:[data parent]] map[field:[data child]]] ts:1656367373130000 unique:true]
{ref: ref(id = "unique_personLink_parent_child", collection = ref(id = "indexes")), ts: 1651094261520000, active: true, serialized: true, unique: true, name: "unique_personLink_parent_child", source: ref(id = "PersonLink", collection = ref(id = "collections")), terms: [{field: ["data", "parent"]}, {field: ["data", "child"]}], partitions: 1}
{ ref: Index("unique_personLink_parent_child"), ts: 1651094261520000, active: true, serialized: true, name: "unique_personLink_parent_child", unique: true, source: Collection("PersonLink"), terms: [ { field: ["data", "parent"] }, { field: ["data", "child"] } ], partitions: 1 }
{'ref': Ref(id=unique_personLink_parent_child, collection=Ref(id=indexes)), 'ts': 1656004338420000, 'active': True, 'serialized': True, 'name': 'unique_personLink_parent_child', 'source': Ref(id=PersonLink, collection=Ref(id=collections)), 'unique': True, 'terms': [{'field': ['data', 'parent']}, {'field': ['data', 'child']}], 'partitions': 1}
{ ref: Index("unique_personLink_parent_child"), ts: 1651094261520000, active: true, serialized: true, name: 'unique_personLink_parent_child', unique: true, source: Collection("PersonLink"), terms: [ { field: [ 'data', 'parent' ] }, { field: [ 'data', 'child' ] } ], partitions: 1 }
-
Query all children and parent relations of a person
Now that the parent/child relations are in place, let’s take a look to verify.
For the next query, copy the
_id
value for thePerson1
document (in the response for step 5) and replace the<$PERSON_ID>
string with that value.{ "data": { "findPersonByID": { "name": "Person1", "parents": { "data": [ { "parent": { "name": "Parent1" } }, { "parent": { "name": "Parent2" } } ] }, "children": { "data": [ { "child": { "name": "Child1" } }, { "child": { "name": "Child2" } }, { "child": { "name": "Child3" } } ] } } } }
Conclusion
Is this article helpful?
Tell Fauna how the article can be improved:
Visit Fauna's forums
or email docs@fauna.com
Thank you for your feedback!