Modeling relationships
Applications that work with data need to define the entities that represent said data. Entities are typically stored across different tables in a database, and items in a given table need a way to be uniquely identified. This is achieved by defining a field (or a combination of fields) that will hold a value (or values) to unambiguously refer to the item. When modeling applications, there is often the need to establish relationships between the entities. These relationships are achieved by having a field that holds the value of the unique identifier of the related entity.
Sometimes when retrieving data for a given entity, retrieving its relationships at the same time can be an expensive operation; for this reason it is important to offer a mechanism for you to specify if relationships should be lazily or eagerly retrieved. Amplify Data provides this mechanism with a "custom selection set" feature.
Types of relationships
Relationship | Description |
---|---|
a.hasMany() | Create a one-directional one-to-many relationship between two models. For example, a Post has many comments. This allows you to query all the comments from the post record. |
a.manyToMany() | Creates a bi-directional relationship, it needs to be defined on both related models. For example, a Blog has many Tags and a Tag has many Blogs. |
a.hasOne() | Create a one-directional one-to-one relationship between two models. For example, a Project "has one" Team. This allows you to query the team from the project record. |
a.belongsTo() | Use a "belongs to" relationship to make a "has one" or "has many" relationship bi-directional. This bi-directionality only facilitates your ability to read from both sides of the relationship. For example, a Project has one Team and a Team belongs to a Project. This allows you to reference the team from the project record and vice versa. |
Model a "Has Many" relationship
Create a bi-directional one-to-many relationship between two models using the hasMany()
method. In the example below, a Team has many Members and the parent Team may be looked up from a given member.
1const schema = a.schema({2 Team: a.model({3 mantra: a.string().required(),4 members: a.hasMany('Member'),5 }),6 Member: a.model({7 team: a.belongsTo('Team'),8 }),9});
Create a "Has Many" relationship between records
1const { data: team } = await client.models.Team.create({2 mantra: "Hello"3})4
5await client.models.Member.update({6 id: "SOME_MEMBER_ID",7 team8})
Update a "Has Many" relationship between records
1await client.models.Member.update({2 id: "SOME_MEMBER_ID",3 team4})
Delete a "Has Many" relationship between records
1await client.models.Member.update({2 id: "SOME_MEMBER_ID",3 team: undefined4})
Lazy load a "Has Many" relationships
1const { data: team } = await client.models.Team.get({ id: "MY_TEAM_ID"})2
3const { data: members } = await team.members()4
5members.forEach(member => console.log(member.id))
Eagerly load a "Has Many" relationships
1const { data: teamWithMembers } = await client.models.Team.get(2 { id: "MY_TEAM_ID" },3 { selectionSet: ["id", "members.*"] },4)5
6teamWithMembers.members.forEach(member => console.log(member.id))
Model a "Many-to-many" relationship
Create a many-to-many relationship between two models with the manyToMany()
method. Provide a common relationName
on both models to join them into a many-to-many relationship.
1const schema = a.schema({2 Post: a.model({3 title: a.string(),4 content: a.string(),5 tags: a.manyToMany('Tag', { relationName: 'PostTags'})6 }),7 Tag: a.model({8 name: a.string(),9 posts: a.manyToMany('Post', { relationName: 'PostTags'})10 }),11});
Model a "Has One" relationship
Create a one-directional one-to-one relationship between two models using the hasOne()
method. In the example below, a Project has a Team. By using hasOne
this way, it will automatically generate a field to hold the identifier of the related model.
1const schema = a.schema({2 Project: a.model({3 name: a.string(),4 team: a.hasOne('Team')5 }),6 Team: a.model({7 name: a.string(),8 }),9});
Create a "Has One" relationship between records
To create a "has one" relationship between records, first create the parent item and then create the child item and assign the parent.
1const { data: team } = await client.models.Team.create({2 name: 'My Team',3});4
5const { data: project } = await client.models.Project.create({6 name: 'My Project',7 team,8});
Update a "Has One" relationship between records
To update a "Has One" relationship between records, you first have to get the child item and then update the reference to the parent to another parent.
1const { data: newTeam } = await client.models.Team.create({2 name: 'New Team',3});4
5await client.models.Project.update({6 id: project.id,7 team: newTeam,8});
Delete a "Has One" relationship between records
You can update the relationship field to delete a "Has One" relationship between records.
1await client.models.Project.update({2 id: project.id,3 team: undefined,4});
Lazy load a "Has One" relationships
1const { data: project } = await client.models.Project.get({ id: "MY_PROJECT_ID"})2const { data: team } = await project.team();
Eagerly load a "Has One" relationships
1const { data: project } = await client.models.Project.get(2 { id: "MY_PROJECT_ID" },3 { selectionSet: ['id', 'team.*'] },4);
Model multiple relationships between two models
1const schema = a.schema({2 Employee: a.model({3 firstName: a.string(),4 lastName: a.string(),5 }),6 Organization: a.model({7 name: a.string(),8 individualContributors: a.hasMany('Employee'),9 managers: a.hasMany('Employee'),10 }),11});
1const client = generateClient<Schema>();2
3const { data: org } = await client.models.Organization.get({ id: 'AE6191BF-FEB4-4D8B-867D-898A7DA33101' });4
5const { data: ics } = await org.individualContributors();6const { data: managers } = await org.managers();
Model a bi-directional relationship
You can make a "has one" or "has many" relationship bi-directional with the belongsTo()
method. The bi-directionality of belongsTo()
grants you the ability to query the related item from both sides of the relationship.
Bi-directional Has One relationship
1const schema = a.schema({2 Project: a.model({3 name: a.string(),4 team: a.hasOne('Team')5 }),6 Team: a.model({7 name: a.string(),8 project: a.belongsTo('Project'),9 }),10});
1const client = generateClient<Schema>();2
3const { data: team } = await client.models.Team.get({ id: 'FABF27EE-25D2-4851-AE1C-09BCA1604C77' });4
5console.log((await team.project()).data?.name); // There is a 'project' field in the team6
7const { data: teamProject } = await team.project();8const { data: teamProjectRelatedTeam } = await teamProject.team();9// teamProjectRelatedTeam is equivalent to original team
Bi-directional Has Many relationship
1const schema = a.schema({2 Post: a.model({3 title: a.string(),4 comments: a.hasMany('Comment'),5 }),6 Comment: a.model({7 content: a.string(),8 post: a.belongsTo('Post'),9 }),10});
1const client = generateClient<Schema>();2
3const { data: post } = await client.models.Post.get({ id: 'FABF27EE-25D2-4851-AE1C-09BCA1604C77' });4
5const { data: postComments } = await post.comments();6const firstComment = postComments[0];7const { data: firstCommentRelatedPost } = await firstComment.post();8// firstCommentRelatedPost should be equivalent to post
Retrieve related models
1const schema = a.schema({2 Project: a.model({3 name: a.string(),4 team: a.hasOne('Team'),5 }),6 Team: a.model({7 name: a.string(),8 }),9});10
11const client = generateClient<Schema>();12
13const { data: project } = await client.models.Project.get({ id: '1234' });14const projectName = project.name;15const lazyTeamName = (await project.team()).data?.name // project.team is typed as `Lazy<Team>` and can be awaited16
17const projectWithTeam = await client.models.Project.get({ id: '1234' }, { selectionSet: ['team.*'] });18const eagerTeamName = project.team.name // All team attributes were requested, so no need to await, it was eagerly loaded