Add relationships between types

You are currently viewing the legacy GraphQL Transformer documentation. View latest documentation

@connection

The @connection directive enables you to specify relationships between @model types. Currently, this supports one-to-one, one-to-many, and many-to-one relationships. You may implement many-to-many relationships using two one-to-many connections and a joining @model type. See the usage section for details.

We also provide a fully working schema with 17 patterns related to relational designs.

Definition

1directive @connection(keyName: String, fields: [String!]) on FIELD_DEFINITION

Usage

Relationships between types are specified by annotating fields on an @model object type with the @connection directive.

The fields argument can be provided and indicates which fields can be queried by to get connected objects. The keyName argument can optionally be used to specify the name of secondary index (an index that was set up using @key) that should be queried from the other type in the relationship.

When specifying a keyName, the fields argument should be provided to indicate which field(s) will be used to get connected objects. If keyName is not provided, then @connection queries the target table's primary index.

Has one

In the simplest case, you can define a one-to-one connection where a project has one team:

1type Project @model {
2 id: ID!
3 name: String
4 team: Team @connection
5}
6
7type Team @model {
8 id: ID!
9 name: String!
10}

You can also define the field you would like to use for the connection by populating the first argument to the fields array and matching it to a field on the type:

1type Project @model {
2 id: ID!
3 name: String
4 teamID: ID!
5 team: Team @connection(fields: ["teamID"])
6}
7
8type Team @model {
9 id: ID!
10 name: String!
11}

In this case, the Project type has a teamID field added as an identifier for the team that the project belongs to. @connection can then get the connected Team object by querying the Team table with this teamID.

After it's transformed, you can create projects and query the connected team as follows:

1mutation CreateProject {
2 createProject(input: { name: "New Project", teamID: "a-team-id" }) {
3 id
4 name
5 team {
6 id
7 name
8 }
9 }
10}

Note: The Project.team resolver is configured to work with the defined connection. This is done with a query on the Team table where teamID is passed in as an argument.

A Has One @connection can only reference the primary index of a model (ie. it cannot specify a "keyName" as described below in the Has Many section).

Has many

The following schema defines a Post that can have many comments:

1type Post @model {
2 id: ID!
3 title: String!
4 comments: [Comment] @connection(keyName: "byPost", fields: ["id"])
5}
6
7type Comment @model @key(name: "byPost", fields: ["postID", "content"]) {
8 id: ID!
9 postID: ID!
10 content: String!
11}

Note how a one-to-many connection needs an @key that allows comments to be queried by the postID and the connection uses this key to get all comments whose postID is the id of the post was called on. After it's transformed, you can create comments and query the connected Post as follows:

1mutation CreatePost {
2 createPost(input: { id: "a-post-id", title: "Post Title" }) {
3 id
4 title
5 }
6}
7
8mutation CreateCommentOnPost {
9 createComment(
10 input: { id: "a-comment-id", content: "A comment", postID: "a-post-id" }
11 ) {
12 id
13 content
14 }
15}

And you can query a Post with its comments as follows:

1query getPost {
2 getPost(id: "a-post-id") {
3 id
4 title
5 comments {
6 items {
7 id
8 content
9 }
10 }
11 }
12}

Belongs to

You can make a connection bi-directional by adding a many-to-one connection to types that already have a one-to-many connection. In this case you add a connection from Comment to Post since each comment belongs to a post:

1type Post @model {
2 id: ID!
3 title: String!
4 comments: [Comment] @connection(keyName: "byPost", fields: ["id"])
5}
6
7type Comment @model @key(name: "byPost", fields: ["postID", "content"]) {
8 id: ID!
9 postID: ID!
10 content: String!
11 post: Post @connection(fields: ["postID"])
12}

After it's transformed, you can create comments with a post as follows:

1mutation CreatePost {
2 createPost(input: { id: "a-post-id", title: "Post Title" }) {
3 id
4 title
5 }
6}
7
8mutation CreateCommentOnPost1 {
9 createComment(
10 input: {
11 id: "a-comment-id-1"
12 content: "A comment #1"
13 postID: "a-post-id"
14 }
15 ) {
16 id
17 content
18 }
19}
20
21mutation CreateCommentOnPost2 {
22 createComment(
23 input: {
24 id: "a-comment-id-2"
25 content: "A comment #2"
26 postID: "a-post-id"
27 }
28 ) {
29 id
30 content
31 }
32}

And you can query a Comment with its Post, then all Comments of that Post by navigating the connection:

1query GetCommentWithPostAndComments {
2 getComment(id: "a-comment-id-1") {
3 id
4 content
5 post {
6 id
7 title
8 comments {
9 items {
10 id
11 content
12 }
13 }
14 }
15 }
16}

Many-to-many connections

You can implement many to many using two 1-M @connections, an @key, and a joining @model. For example:

1type Post @model {
2 id: ID!
3 title: String!
4 editors: [PostEditor] @connection(keyName: "byPost", fields: ["id"])
5}
6
7# Create a join model and disable queries as you don't need them
8# and can query through Post.editors and User.posts
9type PostEditor
10 @model(queries: null)
11 @key(name: "byPost", fields: ["postID", "editorID"])
12 @key(name: "byEditor", fields: ["editorID", "postID"]) {
13 id: ID!
14 postID: ID!
15 editorID: ID!
16 post: Post! @connection(fields: ["postID"])
17 editor: User! @connection(fields: ["editorID"])
18}
19
20type User @model {
21 id: ID!
22 username: String!
23 posts: [PostEditor] @connection(keyName: "byEditor", fields: ["id"])
24}

This case is a bidirectional many-to-many which is why two @key calls are needed on the PostEditor model. You can first create a Post and a User, and then add a connection between them with by creating a PostEditor object as follows:

1mutation CreateData {
2 p1: createPost(input: { id: "P1", title: "Post 1" }) {
3 id
4 }
5 p2: createPost(input: { id: "P2", title: "Post 2" }) {
6 id
7 }
8 u1: createUser(input: { id: "U1", username: "user1" }) {
9 id
10 }
11 u2: createUser(input: { id: "U2", username: "user2" }) {
12 id
13 }
14}
15
16mutation CreateLinks {
17 p1u1: createPostEditor(input: { id: "P1U1", postID: "P1", editorID: "U1" }) {
18 id
19 }
20 p1u2: createPostEditor(input: { id: "P1U2", postID: "P1", editorID: "U2" }) {
21 id
22 }
23 p2u1: createPostEditor(input: { id: "P2U1", postID: "P2", editorID: "U1" }) {
24 id
25 }
26}

Note that neither the User type nor the Post type have any identifiers of connected objects. The connection info is held entirely inside the PostEditor objects.

You can query a given user with their posts:

1query GetUserWithPosts {
2 getUser(id: "U1") {
3 id
4 username
5 posts {
6 items {
7 post {
8 title
9 }
10 }
11 }
12 }
13}

Also you can query a given post with the editors of that post and can list the posts of those editors, all in a single query:

1query GetPostWithEditorsWithPosts {
2 getPost(id: "P1") {
3 id
4 title
5 editors {
6 items {
7 editor {
8 username
9 posts {
10 items {
11 post {
12 title
13 }
14 }
15 }
16 }
17 }
18 }
19 }
20}

Amplify Studio does not support custom naming. Changing the auto-generated name will break Amplify Studio.

Alternative definition

The above definition is the recommended way to create relationships between model types in your API. This involves defining index structures using @key and connection resolvers using @connection. There is an older parameterization of @connection that creates indices and connection resolvers that is still functional for backwards compatibility reasons. It is recommended to use @key and the new @connection via the fields argument.

1directive @connection(
2 name: String
3 keyField: String
4 sortField: String
5 limit: Int
6) on FIELD_DEFINITION

This parameterization is not compatible with @key. See the parameterization above to use @connection with indexes created by @key.

Limit

The default number of nested objects is 100. You can override this behavior by setting the limit argument. For example:

1type Post @model {
2 id: ID!
3 title: String!
4 comments: [Comment] @connection(limit: 50)
5}
6
7type Comment @model {
8 id: ID!
9 content: String!
10}

Generates

In order to keep connection queries fast and efficient, the GraphQL transform manages global secondary indexes (GSIs) on the generated tables on your behalf when using @connection

Note: After you have pushed a @connection directive you should not try to change it. If you try to change it, the DynamoDB UpdateTable operation will fail. Should you need to change a @connection, you should add a new @connection that implements the new access pattern, update your application to use the new @connection, and then delete the old @connection when it's no longer needed.