Page updated Jan 16, 2024

Relational models

The @hasOne and @hasMany directives do not support referencing a model which then references the initial model via @hasOne or @hasMany if DataStore is enabled.

DataStore has the capability to handle relationships between Models, such as has one, has many, belongs to. In GraphQL this is done with the @hasOne, @hasMany and @index directives as defined in the GraphQL Transformer documentation.

Updated schema

For the examples below with DataStore let's add a new model to the sample schema:

1enum PostStatus {
2 ACTIVE
3 INACTIVE
4}
5
6type Post @model {
7 id: ID!
8 title: String!
9 rating: Int!
10 status: PostStatus!
11 # New field with @hasMany
12 comments: [Comment] @hasMany
13}
14
15# New model
16type Comment @model {
17 id: ID!
18 post: Post @belongsTo
19 content: String!
20}

Saving relations

In order to save connected models, you will create an instance of the model you wish to connect and pass its ID to DataStore.save:

1const post = await DataStore.save(
2 new Post({
3 title: 'My Post with comments',
4 rating: 10,
5 status: PostStatus.ACTIVE
6 })
7);
8
9await DataStore.save(
10 new Comment({
11 content: 'Loving Amplify DataStore!',
12 post: post
13 })
14);

Querying relations

You can query related models in three ways:

  • Option 1: use the toArray() function to lazy load related comments
1const post = await DataStore.query(Post, "YOUR_POST_ID")
2if (post) {
3 const comments = await post.comments.toArray()
4}
1const post = await DataStore.query(Post, "YOUR_POST_ID");
2const comments = await post.comments.toArray();
  • Option 2: use async iterators to lazy load related comments
1const post = await DataStore.query(Post, "YOUR_POST_ID");
2if (post) {
3 for await (const comment of post.comments) {
4 console.log(comment)
5 };
6}
1const post = await DataStore.query(Post, "YOUR_POST_ID");
2for await (const comment of post.comments) {
3 console.log(comment)
4};
  • Option 3: use nested query predicates with the Comment model
1const comments = await DataStore.query(Comment, c => c.post.id.eq('YOUR_POST_ID'));

Deleting relations

When you delete a parent object in a one-to-many relationship, the children will also be removed from the DataStore and mutations for this deletion will be synced to cloud. For example, the following operation would remove the Post with id 123 as well as any related comments:

1const toDelete = await DataStore.query(Post, "123");
2if (toDelete) {
3 DataStore.delete(toDelete);
4}
1const toDelete = await DataStore.query(Post, "123");
2DataStore.delete(toDelete);

However, in a many-to-many relationship the children are not removed and you must explicitly delete them.

Many-to-many relationships

For many-to-many relationships, you can use the @manyToMany directive and specify a relationName. Under the hood, Amplify creates a join table and a one-to-many relationship from both models.

Join table records must be deleted prior to deleting the associated records. For example, for a many-to-many relationship between Posts and Tags, delete the PostTags join record prior to deleting a Post or Tag.

1enum PostStatus {
2 ACTIVE
3 INACTIVE
4}
5
6type Post @model {
7 id: ID!
8 title: String!
9 rating: Int
10 status: PostStatus
11 editors: [User] @manyToMany(relationName: "PostEditor")
12}
13
14type User @model {
15 id: ID!
16 username: String!
17 posts: [Post] @manyToMany(relationName: "PostEditor")
18}
1// first you save the post
2const post = await DataStore.save(
3 new Post({
4 title: 'My first post'
5 })
6);
7
8// secondly, you save the editor/user
9const editor = await DataStore.save(
10 new User({
11 username: 'Nadia'
12 })
13);
14
15// then you save the model that links a post with an editor
16await DataStore.save(
17 new PostEditor({
18 post: post,
19 user: editor
20 })
21);

Create

In order to save a many-to-many relationship, create both model instance first and then save them to a new join model instance.

1const post = await DataStore.save(
2 new Post({
3 title: 'Amplify Weekly',
4 rating: 10,
5 status: PostStatus.ACTIVE
6 })
7);
8
9const user = await DataStore.save(
10 new User({
11 username: 'rene'
12 })
13);
14
15const postEditor = await DataStore.save(
16 new PostEditor({
17 post: post,
18 user: user
19 })
20);

Here we've first created a new Post instance and a new User instance. Then, saved those instances to a new instance of our join model PostEditor.

Query

To query many-to-many relationships, use a nested predicate to filter the target model by the source model's id:

1const editors = await DataStore.query(User, (u) => u.posts.post.id.eq(post.id));

Delete

Deleting the join model instance will not delete any source model instances.

1await DataStore.delete(toBeDeletedPostEditor);

Both the Post and the User instances will not be deleted. Only the join model instances containing the link between a Post and a User.

Deleting a source model instance will also delete the join model instances containing the source model instance.

1await DataStore.delete(toBeDeletedUser);

The toBeDeletedUser User instance and all PostEditor instances where user is linked to toBeDeletedUser will be deleted.