Page updated Nov 20, 2023

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

RelationshipDescription
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.

const schema = a.schema({ Team: a.model({ mantra: a.string().required(), members: a.hasMany('Member'), }), Member: a.model({ team: a.belongsTo('Team'), }), });
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

const { data: team } = await client.models.Team.create({ mantra: "Hello" }) await client.models.Member.update({ id: "SOME_MEMBER_ID", team })
1const { data: team } = await client.models.Team.create({
2 mantra: "Hello"
3})
4
5await client.models.Member.update({
6 id: "SOME_MEMBER_ID",
7 team
8})

Update a "Has Many" relationship between records

await client.models.Member.update({ id: "SOME_MEMBER_ID", team })
1await client.models.Member.update({
2 id: "SOME_MEMBER_ID",
3 team
4})

Delete a "Has Many" relationship between records

await client.models.Member.update({ id: "SOME_MEMBER_ID", team: undefined })
1await client.models.Member.update({
2 id: "SOME_MEMBER_ID",
3 team: undefined
4})

Lazy load a "Has Many" relationships

const { data: team } = await client.models.Team.get({ id: "MY_TEAM_ID"}) const { data: members } = await team.members() members.forEach(member => console.log(member.id))
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

const { data: teamWithMembers } = await client.models.Team.get( { id: "MY_TEAM_ID" }, { selectionSet: ["id", "members.*"] }, ) teamWithMembers.members.forEach(member => console.log(member.id))
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.

const schema = a.schema({ Post: a.model({ title: a.string(), content: a.string(), tags: a.manyToMany('Tag', { relationName: 'PostTags'}) }), Tag: a.model({ name: a.string(), posts: a.manyToMany('Post', { relationName: 'PostTags'}) }), });
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.

const schema = a.schema({ Project: a.model({ name: a.string(), team: a.hasOne('Team') }), Team: a.model({ name: a.string(), }), });
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.

const { data: team } = await client.models.Team.create({ name: 'My Team', }); const { data: project } = await client.models.Project.create({ name: 'My Project', team, });
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.

const { data: newTeam } = await client.models.Team.create({ name: 'New Team', }); await client.models.Project.update({ id: project.id, team: newTeam, });
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.

await client.models.Project.update({ id: project.id, team: undefined, });
1await client.models.Project.update({
2 id: project.id,
3 team: undefined,
4});

Lazy load a "Has One" relationships

const { data: project } = await client.models.Project.get({ id: "MY_PROJECT_ID"}) const { data: team } = await project.team();
1const { data: project } = await client.models.Project.get({ id: "MY_PROJECT_ID"})
2const { data: team } = await project.team();

Eagerly load a "Has One" relationships

const { data: project } = await client.models.Project.get( { id: "MY_PROJECT_ID" }, { selectionSet: ['id', 'team.*'] }, );
1const { data: project } = await client.models.Project.get(
2 { id: "MY_PROJECT_ID" },
3 { selectionSet: ['id', 'team.*'] },
4);

Model multiple relationships between two models

const schema = a.schema({ Employee: a.model({ firstName: a.string(), lastName: a.string(), }), Organization: a.model({ name: a.string(), individualContributors: a.hasMany('Employee'), managers: a.hasMany('Employee'), }), });
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});
const client = generateClient<Schema>(); const { data: org } = await client.models.Organization.get({ id: 'AE6191BF-FEB4-4D8B-867D-898A7DA33101' }); const { data: ics } = await org.individualContributors(); const { data: managers } = await org.managers();
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.

Note: The belongsTo() method requires that a hasOne() or hasMany() relationship already exists from parent to the related model.

Bi-directional Has One relationship

const schema = a.schema({ Project: a.model({ name: a.string(), team: a.hasOne('Team') }), Team: a.model({ name: a.string(), project: a.belongsTo('Project'), }), });
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});
const client = generateClient<Schema>(); const { data: team } = await client.models.Team.get({ id: 'FABF27EE-25D2-4851-AE1C-09BCA1604C77' }); console.log((await team.project()).data?.name); // There is a 'project' field in the team const { data: teamProject } = await team.project(); const { data: teamProjectRelatedTeam } = await teamProject.team(); // teamProjectRelatedTeam is equivalent to original team
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 team
6
7const { data: teamProject } = await team.project();
8const { data: teamProjectRelatedTeam } = await teamProject.team();
9// teamProjectRelatedTeam is equivalent to original team

"Belongs to"-relationships do not allow you to update both sides of a "has one" and "belongs to" relationship in a transactional nature.

Bi-directional Has Many relationship

const schema = a.schema({ Post: a.model({ title: a.string(), comments: a.hasMany('Comment'), }), Comment: a.model({ content: a.string(), post: a.belongsTo('Post'), }), });
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});
const client = generateClient<Schema>(); const { data: post } = await client.models.Post.get({ id: 'FABF27EE-25D2-4851-AE1C-09BCA1604C77' }); const { data: postComments } = await post.comments(); const firstComment = postComments[0]; const { data: firstCommentRelatedPost } = await firstComment.post(); // firstCommentRelatedPost should be equivalent to post
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
const schema = a.schema({ Project: a.model({ name: a.string(), team: a.hasOne('Team'), }), Team: a.model({ name: a.string(), }), }); const client = generateClient<Schema>(); const { data: project } = await client.models.Project.get({ id: '1234' }); const projectName = project.name; const lazyTeamName = (await project.team()).data?.name // project.team is typed as `Lazy<Team>` and can be awaited const projectWithTeam = await client.models.Project.get({ id: '1234' }, { selectionSet: ['team.*'] }); const eagerTeamName = project.team.name // All team attributes were requested, so no need to await, it was eagerly loaded
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 awaited
16
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