In this blog, we will learn how to mutate and query many-to-many relations in AWS Amplify using React.
Let’s understand the scenario with the help of an example:
In a university, there are many clubs and each club has many students. A student can also be a part of more than one club. So, we have an M: M (many-to-many) relationship.
There are many techniques to understand and decode this relationship. Here, we will do this with the help of an auxiliary table.
This is also shown in the data model below:
Suppose we have 2 clubs, Technovation and Codechef which have 20 students and 15 students respectively. Out of these students, there may be many who are a part of both clubs.
Before getting started with mutations and queries in this example, let’s brush up on the technologies used and a few common terms and directives that we are going to use.
GraphQL: GraphQL is a query language that is used to create, modify, and fetch data from the data sources. With GraphQL, the client gets exact data that is queried, nothing more, and nothing less. (That’s one of the main reasons behind using GraphQL over REST).
DynamoDB: We will use DynamoDB as our Data Source. Data sources are basically resources with which APIs interact. DynamoDB stores data in key-value pairs or as documents. It is a NoSQL database and has single-digit millisecond latency.
GraphQL Schema: GraphQL APIs are defined by the schema. The schema defines the data that will flow through our API and how operations (query, mutation, and subscription) will be performed on our data sources. The syntax followed by Schema is SDL(Schema Definition Language). As soon as the schema is changed, run the command
- On running the
amplify pushcommand, GraphQL schema is converted into a set of AWS CloudFormation templates that are uploaded in the cloud. These changes can be found at
- If you change any category(API, Auth, Storage)and run the
amplify pushcommand, AWS CloudFormation API is called to make changes in the cloud and based on those changes,
- @model: This directive creates a table in the DynamoDB and automatically creates resolvers (
onCreate) which can be configured through queries, mutations, and subscription, found under
- @key: This directive allows us to create additional data access patterns. For example: If we have a table of Clubs which has a field “type” (supposing a club can be technical or non-technical) then the @key directive can help us to fetch clubs based on its “type”(technical or non-technical).
To sort or fetch data using @key:
fieldsargument can have any number of values. The first entry in the list will always be a Hash Key. If there are two entries, then the second field is sort Key and if there are more than two entries, then a single composite sort key is created for all fields, which can be found in the table as
secondEntry#thirdEntry. Remember, the first entry is always a hash key.
By using the @key directive, any data can be accessed with just a single line code.
- @connection: This directive helps to specify relationships between different tables. This directive supports one-to-one, one-to-many, and many-to-one relationships. For many-to-many relationships, we need to use two one-to-many relationships
3.1 GraphQL Schema-
Club- This table contains information about our clubs(name, ID, type).
Student- This table contains information about our students(name and ID).
StudentClubJoin- This table contains information about our connections and stores the reference of connected tables.
We will establish a one-to-many relationship between the Club table and
StudentClubJoin table. Similarly, between the Student table and
StudentClubJoin table and then connect both tables through
Note: Fields marked with “!” are mandatory.
On running the
amplify push command, the
@model directive will create DynamoDB tables and resolvers.
@connection directive will help connect the Club table with the
StudentClubJoin table by creating the index
StudentClubJoinClubId in the
StudentClubJoin table. Similarly, the Student table is connected to the
StudentClubJoin table by the
This is the older way of connecting tables where indices are created by
The recommended way is to use keys where the index structure is created by
@key directive and connection resolvers by
@connections. So, let’s now design a schema using two 1-M
@key and a joining
The recommended way is as follows:
When we run
@key directive will create a
clubID index for the club table and
studentID index for the student table as a reference in the
@connection directive, the
fields argument is provided to indicate the fields that will be used to get connected objects. The
fields argument can only have ID type data because we will use this data to query connected objects.
keyName argument (in
@connection directive) and the
name argument in
@key directive should have the same value.
Above is a screenshot of the
Student table, and
StudentClubJoin table (using keys). As you can see, the ID of the Club entry is stored in the
StudentClubJoin table as
clubID and that of the student is stored as
Without keys (schema-1), the indexes will be
Since our tables are connected, we will now create a club and save its ID. Similarly, we will create a student and save its ID. Then, we need to pass both the IDs to our connecting table i.e.
Note: If you want to use the first schema, then change
StudentClubJoinStudentId everywhere (except for schema because
@connections directive has already done that for you).
Note: If we don’t pass the ID while mutating, a unique ID is generated automatically.
To query data we have-
getStudent. By using listClubs:, we get the whole list, whereas, by using getClub, we get data specific to the particular ID entered. getClub doesn’t work if no ID is provided.
In our club model, we have a student field, which is connected to the
StudentClubJoin table and in the
StudentClubJoin table, Student’s ID and Club’s ID is stored. Now, when we want to fetch all the students enrolled in a particular club, we have 2 options:
getClubsto get the details of a particular club.
getClubswill give us club details (clubName, id, createdAt,updatedAt, type ) and student details (id, studentID, clubID, createdAt and updatedAt). We can then use
getStudentto fetch details of students by passing
studentIDas its required input ID.
src/graphql/queries.js, we have
getClubwhich we will need to change but we can’t change this in
queries.jsbecause queries, mutations, and subscription files are updated whenever we make changes in
graphql.schema(and by running
So, we can make a custom file inside
src/graphql by any name and change it to the following code:
Now, if we run the following code, we will get the required result:
The second approach is preferred because it provides data in the required format and also reduces the network calls, however, the first method can also be used.
...and that is how you develop, mutate and query many-to-many relations in AWS Amplify using GraphQL.
Hope this article helps!
Thanks for reading :)