Name:
interface
Value:
Amplify has re-imagined the way frontend developers build fullstack applications. Develop and deploy without the hassle.

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

directive @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:

type Project @model {
id: ID!
name: String
team: Team @connection
}
type Team @model {
id: ID!
name: String!
}

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:

type Project @model {
id: ID!
name: String
teamID: ID!
team: Team @connection(fields: ["teamID"])
}
type Team @model {
id: ID!
name: String!
}

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:

mutation CreateProject {
createProject(input: { name: "New Project", teamID: "a-team-id" }) {
id
name
team {
id
name
}
}
}

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:

type Post @model {
id: ID!
title: String!
comments: [Comment] @connection(keyName: "byPost", fields: ["id"])
}
type Comment @model @key(name: "byPost", fields: ["postID", "content"]) {
id: ID!
postID: ID!
content: String!
}

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:

mutation CreatePost {
createPost(input: { id: "a-post-id", title: "Post Title" }) {
id
title
}
}
mutation CreateCommentOnPost {
createComment(
input: { id: "a-comment-id", content: "A comment", postID: "a-post-id" }
) {
id
content
}
}

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

query getPost {
getPost(id: "a-post-id") {
id
title
comments {
items {
id
content
}
}
}
}

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:

type Post @model {
id: ID!
title: String!
comments: [Comment] @connection(keyName: "byPost", fields: ["id"])
}
type Comment @model @key(name: "byPost", fields: ["postID", "content"]) {
id: ID!
postID: ID!
content: String!
post: Post @connection(fields: ["postID"])
}

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

mutation CreatePost {
createPost(input: { id: "a-post-id", title: "Post Title" }) {
id
title
}
}
mutation CreateCommentOnPost1 {
createComment(
input: {
id: "a-comment-id-1"
content: "A comment #1"
postID: "a-post-id"
}
) {
id
content
}
}
mutation CreateCommentOnPost2 {
createComment(
input: {
id: "a-comment-id-2"
content: "A comment #2"
postID: "a-post-id"
}
) {
id
content
}
}

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

query GetCommentWithPostAndComments {
getComment(id: "a-comment-id-1") {
id
content
post {
id
title
comments {
items {
id
content
}
}
}
}
}

Many-to-many connections

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

type Post @model {
id: ID!
title: String!
editors: [PostEditor] @connection(keyName: "byPost", fields: ["id"])
}
# Create a join model and disable queries as you don't need them
# and can query through Post.editors and User.posts
type PostEditor
@model(queries: null)
@key(name: "byPost", fields: ["postID", "editorID"])
@key(name: "byEditor", fields: ["editorID", "postID"]) {
id: ID!
postID: ID!
editorID: ID!
post: Post! @connection(fields: ["postID"])
editor: User! @connection(fields: ["editorID"])
}
type User @model {
id: ID!
username: String!
posts: [PostEditor] @connection(keyName: "byEditor", fields: ["id"])
}

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:

mutation CreateData {
p1: createPost(input: { id: "P1", title: "Post 1" }) {
id
}
p2: createPost(input: { id: "P2", title: "Post 2" }) {
id
}
u1: createUser(input: { id: "U1", username: "user1" }) {
id
}
u2: createUser(input: { id: "U2", username: "user2" }) {
id
}
}
mutation CreateLinks {
p1u1: createPostEditor(input: { id: "P1U1", postID: "P1", editorID: "U1" }) {
id
}
p1u2: createPostEditor(input: { id: "P1U2", postID: "P1", editorID: "U2" }) {
id
}
p2u1: createPostEditor(input: { id: "P2U1", postID: "P2", editorID: "U1" }) {
id
}
}

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:

query GetUserWithPosts {
getUser(id: "U1") {
id
username
posts {
items {
post {
title
}
}
}
}
}

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:

query GetPostWithEditorsWithPosts {
getPost(id: "P1") {
id
title
editors {
items {
editor {
username
posts {
items {
post {
title
}
}
}
}
}
}
}
}

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.

directive @connection(
name: String
keyField: String
sortField: String
limit: Int
) 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:

type Post @model {
id: ID!
title: String!
comments: [Comment] @connection(limit: 50)
}
type Comment @model {
id: ID!
content: String!
}

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.