GraphQL Recipes (V2) - Building APIs with GraphQL Transform
Tags: graphql, react, javascript
Published: 2019-08-08 13:58:18

---
title: GraphQL Recipes (V2) - Building APIs with GraphQL Transform 
published: true
description: Reusable annotated GraphQL schemas.
tags: graphql, react, javascript
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/75n04fdn94bbt8wrbw3u.jpg
---

This is __V2__ of this post, updated with new schemas and the most up to date directives from Amplify.

> Cover photo by [Tarn Nguyen on Unsplash](https://unsplash.com/@sqrlsm)

> To view the repo only containing the code and instructions for deploying these applications, click [here](https://github.com/dabit3/graphql-app-recipes).

In my post [Infrastructure as Code in the Era of GraphQL and Full Stack Serverless](https://dev.to/dabit3/infrastructure-as-code-in-the-era-of-graphql-and-full-stack-serverless-11bc) I showed how you could leverage GraphQL, the [AWS Amplify CLI](https://docs.amplify.aws/), and the [GraphQL Transform](https://docs.amplify.aws/cli/graphql-transformer/directives) library to build, share, and deploy full stack cloud applications.

In this post I've created annotated GraphQL schemas that you can use to deploy popular types of applications.

---

The [GraphQL Transform](https://docs.amplify.aws/cli/graphql-transformer/directives) library allows you to deploy AWS AppSync GraphQL APIs with features like NoSQL databases, authentication, elasticsearch engines, lambda function resolvers, relationships, authorization, and more using GraphQL schema directives.

For example, take the following schema that is utilizing the `@model` directive:

```graphql
type Note @model {
  id: ID!
  name: String!
  description: String
}
```

This schema will deploy the following:
1. GraphQL API
2. CRUD GraphQL operations for this base type: Create, Read, Update, Delete, and List
3. GraphQL subscriptions (triggered by mutation events; create, update, delete)
4. DynamoDB NoSQL Database
5. GraphQL resolvers mapping the DynamoDB table to the GraphQL CRUD operations

As of this post there are 8 directives offered by the GraphQL Transform library:

```javascript
@model
// Deploys DynamodDB table + resolvers + additional GraphQL schema

@auth
// Allows the definition of auth rules and builds corresponding GraphQL resolvers based on these rules

@connection
// Enables you to specify relationships between `@model` object types 

@searchable
// Handles streaming the data of an @model object type to Amazon Elasticsearch Service and configures search resolvers that search that information

@function
//  Allows you to quickly & easily configure AWS Lambda resolvers within your AWS AppSync API

@key
// Enables you to configure custom data access patterns for @model types

@versioned
// Adds object versioning and conflict resolution to a type

@http
// Allows you to quickly configure HTTP resolvers within your AWS AppSync API
```

Using this library you can deploy the back end for your application using only an annotated GraphQL schema.

In this post I will show example schemas that, when used with the Amplify GraphQL transform library, will build out the backends for many popular types of applications.

1. [Todo App](https://dev.to/open-graphql/graphql-recipes-building-apis-with-graphql-transform-3jp0#todo-app)
2. [Events App](https://dev.to/open-graphql/graphql-recipes-building-apis-with-graphql-transform-3jp0#events-app)
3. [Chat App](#chat-app)
4. [E-Commerce App](https://dev.to/open-graphql/graphql-recipes-building-apis-with-graphql-transform-3jp0#ecommerce-app)
5. [WhatsApp Clone](https://dev.to/open-graphql/graphql-recipes-building-apis-with-graphql-transform-3jp0#whatsapp-clone)
6. [Reddit Clone](https://dev.to/open-graphql/graphql-recipes-building-apis-with-graphql-transform-3jp0#reddit-clone)
7. [Multi-user Chat App](https://dev.to/open-graphql/graphql-recipes-building-apis-with-graphql-transform-3jp0#multiuser-chat-app)
8. [Instagram Clone](https://dev.to/open-graphql/graphql-recipes-building-apis-with-graphql-transform-3jp0#instagram-clone)
9. [Conference App](https://dev.to/open-graphql/graphql-recipes-building-apis-with-graphql-transform-3jp0#conference-app)

> For a tutorial showing how to deploy these applications using the GraphQL transform library, check out the documentation [here](https://aws-amplify.github.io/docs/js/api#automated-configuration-with-cli).

> Some applications may require additional custom authorization logic for certain subscriptions that you may not want accessible to all users. To learn more, check out the documentation [here](https://docs.aws.amazon.com/appsync/latest/devguide/security-authorization-use-cases.html).

## Testing these out

To deploy any of these applications, run the following commands:

> Be sure to first [install the AWS Amplify CLI](https://aws-amplify.github.io/docs/)

```sh
$ amplify init
# Follow the steps to give the project a name, environment name, and set the default text editor.
# Accept defaults for everything else and choose your AWS Profile.

# If the app needs auth, add auth (choose the defaults)
$ amplify add auth

$ amplify add api

> Choose GraphQL
> If using authentication, choose Amazon Cognito as one of the authentication types
> Update GraphQL schema

# if the app needs storage (i.e. images or video)
$ amplify add storage

$ amplify push
```

### Testing locally

You can now use [local mocking](https://aws-amplify.github.io/docs/cli-toolchain/usage#api-mocking-setup) to test serverless GraphQL APIs, databases, and serverless functions locally.

```sh
$ amplify mock
```

Check out this video for a quick overview of local testing:

{% youtube vC9y0qc5PP4 %}

## Todo App

Let's start with something very basic: a Todo app.

This app has the following requirements. The user should be able to:

1. List all Todos
2. Create, update, and delete Todos

Based on these requirements we can assume we need the following for this application:

1. Todo type
2. Database
3. GraphQL definition for mutations (create, update, delete todos)
4. GraphQL definition for queries (listTodos)
5. GraphQL resolvers for all operations

To build this app, we could use the following annotated GraphQL schema:

```graphql
type Todo @model {
  id: ID!
  name: String!
  description: String
}
```

This will deploy the entire GraphQL API including the DynamoDB NoSQL database, additional GraphQL schema for GraphQL CRUD and List operations, GraphQL subscriptions, and the GraphQL resolvers mapping the schema to the database.

## Events App

Next, let's look at how we might create an events app. A point to notice here is that we will need to have a way for only __Admins__ to be able to create, update, and delete events and public or other signed in users (non-Admins) to only be able to read events.

We also want to be able to query and get a sorted list (by date) of the events. There is also an optional `Comment` type that could be implemented to allow comments on an event and a one to many relationship enabled between events and comments using the `@connection` directive.

Based on these requirements, the user should be able to:

1. List events in order by date of event
2. View an individual event
3. Create a comment (signed in users only)
4. View comments on an event

An admin should also be able to:

1. Create an event
2. Update and delete an event

To build this app, we could use the following annotated GraphQL schema:

```graphql
type Event @model
  @key(name: "itemType", fields: ["itemType", "time"], queryField: "eventsByDate")
  @auth(rules: [
    { allow: groups, groups: ["Admin"] },
    { allow: public, operations: [read] },
    { allow: private, operations: [read] }
  ]) {
    id: ID!
    name: String!
    description: String
    time: String!
    itemType: String!
    comments: [Comment] @connection #optional comments field
}

# Optional Comment type
type Comment @model
  @auth(rules: [
    { allow: owner, ownerField: "author" },
    { allow: public, operations: [read] },
    { allow: private, operations: [read] }
  ]) {
  id: ID!
  message: String!
  author: String
}
```

### Creating an event

When creating a new event, we would need to populate the `itemType` parameter with a consistent value in order to be able to sort by time of the event:

```graphql
mutation createEvent {
  createEvent(input: {
    name: "Rap battle of the ages"
    description: "You don't want to miss this!"
    time: "2018-07-13T16:00:00Z"
    itemType: "Event"
  }) {
    id name description time
  } 
}
```

> Note: because `itemType` will be the same ("Event") value across all items, you could also update the resolver request mapping template to auto-populate this value and therefore not need to specify it in the mutation.

Now, to query for a list of sorted events, you could use the following query:

```graphql
query listEvents {
  eventsByDate(itemType: "Event") {
    items {
      id
      name
      description
      time
    }
  }
}
```

Once you've created the authentication by running `amplify add auth`, you can run `amplify console auth` to add a user to the `Admin` group or use a [Lambda Trigger](https://aws.amazon.com/blogs/mobile/amplify-framework-adds-support-for-aws-lambda-functions-and-amazon-dynamodb-custom-indexes-in-graphql-schemas/) to do it automatically when certain users sign up.

### Creating a comment on an event

Using the `eventCommentsId` field, you can specify the event that you would like to associate with the comment:

```graphql
mutation createComment {
  createComment(input: {
    eventCommentsId: "7f0d82f5-b57e-4417-b515-ce04475675a2"
    message:"Amazing!"
  }) {
    id
    message
  }
}
```

## Chat app

In this example we'll look at how to create a basic chat app. For a more real-world chat-app that manages multiple users with authorization, check out the [Multi-user Chat App](#multiuser-chat-app) example.

This app has the following requirements. The user should be able to:

1. Create a conversation
2. Create a message in a conversation
3. View all conversations and messages
4. Subscribe to new messages and conversations in real-time

```graphql
type Conversation @model {
  id: ID!
  name: String
  messages: [Message] @connection(keyName: "messagesByConversationId", fields: ["id"])
  createdAt: String
  updatedAt: String
}

type Message
  @model(subscriptions: null, queries: null)
  @key(name: "messagesByConversationId", fields: ["conversationId"]) {
  id: ID!
  conversationId: ID!
  content: String!
  conversation: Conversation @connection(fields: ["conversationId"])
  createdAt: String
}
```

The following GraphQL queries and mutations would allow users to create a conversation, create a message in a conversation, and view all conversations and messages:

```graphql
mutation createConversation {
  createConversation(input: {
    name: "my first conversation"
  }) {
    name
    id
  }
}

mutation createMessage {
  createMessage(input: {
    conversationId: "your-conversation-id"
    content: "Hello world"
  }) {
    id
    content
  }
}

query listConversations {
  listConversations {
    items {
      name
      messages {
        items {
          content
        }
      }
    }
  }
}
```

## E-commerce App

This app has the following requirements. The Customer should be able to:

1. Create an account
2. View products
3. Create an order
4. Add line items to an order
5. View their account and all associated orders and items

An admin should be able to:

1. Create, update, and delete products, orders and customers
2. Fetch orders, products, and customers
3. Fetch orders by customer id

Based on these requirements we can assume we need the following for this application:

1. Product, Customer, Line Item, and Order types
2. Database tables for Products, Customers, Line Items, and Orders
3. GraphQL definition for mutations (create, update, delete products, customers, and orders)
4. GraphQL definition for queries (get, list)
5. GraphQL resolvers for all operations

To build this app, we could use the following annotated GraphQL schema:

```graphql
type Customer @model(subscriptions: null)
  @auth(rules: [
    { allow: owner },
    { allow: groups, groups: ["Admin"] }
  ]) {
  id: ID!
  name: String!
  email: String!
  address: String
  orders: [Order] @connection(keyName: "byCustomerId", fields: ["id"])
}

type Product @model(subscriptions: null)
  @auth(rules: [
    { allow: groups, groups: ["Admin"] },
    { allow: public, operations: [read] },
    { allow: private, operations: [read] }
  ]) {
  id: ID!
  name: String!
  description: String
  price: Float!
  image: String
}

type LineItem @model(subscriptions: null)
  @key(name: "byOrderId", fields: ["orderId"])
  @auth(rules: [
   { allow: owner },
   { allow: groups, groups: ["Admin"] }
  ]) {
  id: ID!
  orderId: ID!
  productId: ID!
  qty: Int
  order: Order @connection(fields: ["orderId"])
  product: Product @connection(fields: ["productId"])
  description: String
  price: Float
  total: Float
}

type Order @model(subscriptions: null)
  @key(name: "byCustomerId", fields: ["customerId", "createdAt"], queryField: "ordersByCustomerId")
  @auth(rules: [
   { allow: owner },
   { allow: groups, groups: ["Admin"] }
  ]) {
  id: ID!
  customerId: ID!
  total: Float
  subtotal: Float
  tax: Float
  createdAt: String!
  customer: Customer @connection(fields: ["customerId"])
  lineItems: [LineItem] @connection(keyName: "byOrderId", fields: ["id"])
}
```

You should be able to now perform the following GraphQL operations against the API:

```graphql
mutation createProduct {
  createProduct(input: {
    name: "Yeezys"
    description: "Best shoes ever"
    price: 200.00
  }) {
    id
    name
    description
    price
  }
}

mutation createCustomer {
  createCustomer(input: {
    name: "John Doe"
    email: "johndoe@myemail.com"
    address: "555 Hwy 88"
  }) {
    id
    email
    name
    address
  }
}

mutation createOrder {
  createOrder(input: {
    subtotal: 250.00
    total: 275.00
    tax: 25.00
    customerId: "some-customer-id"
  }) {
    id
    subtotal
    tax
    total
    customer {
      name
    }
  }
}

mutation createLineItem {
  createLineItem(input: {
    qty: 1
    productId: "some-product-id"
    orderId: "some-order-id"
    price: 250.00
    total: 250.00
  }) {
    id
    qty
  }
}

query getCustomer {
  getCustomer(id: "some-customer-id") {
    id
    name
    address
    orders {
      items {
        id
        lineItems {
          items {
            description
            price
            total
            qty
            product {
              id
              name
              description
            }
          }
        }
      }
    }
  }
}

query ordersByCustomerId {
  ordersByCustomerId(
    customerId: "some-customer-id"
  ) {
    items {
      id
      lineItems {
        items {
          id
          price
          total
        }
      }
      total
      subtotal
      tax
    }
  }
}

query listOrders {
  listOrders {
    items {
      id
      total
      subtotal
      tax
      lineItems {
        items {
          id
          price
          product {
            id
            price
            description
          }
        }
      }
    }
  }
}
```

## WhatsApp Clone

This app has the following requirements. The User should be able to:

1. Create an account
2. Update their profile with their avatar image
3. Create a conversation
4. Create a message in a conversation

Based on these requirements we can assume we need the following for this application:

1. User, Conversation, and Message types
2. Database tables for Users, Conversations, and Messages
3. GraphQL definition for mutations (create, update, delete users, conversations, and messages)
4. GraphQL definition for queries
5. GraphQL subscriptions for real-time communication
6. GraphQL resolvers for all operations

To build this app, we could use the following annotated GraphQL schema:

```graphql
type User
  @key(fields: ["userId"])
  @model(subscriptions: null)
  @auth(rules: [
    { allow: owner, ownerField: "userId" }
  ]) {
  userId: ID!
  avatar: String
  conversations: [ConvoLink] @connection(keyName: "conversationsByUserId", fields: ["userId"])
  messages: [Message] @connection(keyName: "messagesByUserId", fields: ["userId"])
  createdAt: String
  updatedAt: String
}

type Conversation
  @model(subscriptions: null)
  @auth(rules: [{ allow: owner, ownerField: "members" }]) {
  id: ID!
  messages: [Message] @connection(keyName: "messagesByConversationId", fields: ["id"])
  associated: [ConvoLink] @connection(keyName: "convoLinksByConversationId", fields: ["id"])
  members: [String!]!
  createdAt: String
  updatedAt: String
}

type Message
  @key(name: "messagesByConversationId", fields: ["conversationId"])
  @key(name: "messagesByUserId", fields: ["userId"])
  @model(subscriptions: null, queries: null) {
  id: ID!
  userId: ID!
  conversationId: ID!
  author: User @connection(fields: ["userId"])
  content: String!
  image: String
  conversation: Conversation @connection(fields: ["conversationId"])
  createdAt: String
  updatedAt: String
}

type ConvoLink
  @key(name: "convoLinksByConversationId", fields: ["conversationId"])
  @key(name: "conversationsByUserId", fields: ["userId"])
  @model(
    mutations: { create: "createConvoLink", update: "updateConvoLink" }
    queries: null
    subscriptions: null
  ) {
  id: ID!
  userId: ID!
  conversationId: ID!
  user: User @connection(fields: ["userId"])
  conversation: Conversation @connection(fields: ["conversationId"])
  createdAt: String
  updatedAt: String
}

type Subscription {
  onCreateConvoLink(userId: ID): ConvoLink
    @aws_subscribe(mutations: ["createConvoLink"])
  onCreateMessage(conversationId: ID): Message
    @aws_subscribe(mutations: ["createMessage"])
}
```

## Reddit Clone

This app has the following requirements. The User should be able to:

1. Create an account
2. Create & delete a post (post can be an image or text)
3. Comment on a post
4. Vote on a post
5. Vote on a comment

Based on these requirements we can assume we need the following for this application:

1. User, Post, Comment, and Vote types
2. Database
3. GraphQL definition for mutations (create, update, delete users, posts and comments)
4. GraphQL definition for queries
5. GraphQL resolvers for all operations

To build this app, we could use the following annotated GraphQL schema:

```graphql
type User @model(subscriptions: null)
  @key(fields: ["userId"])
  @auth(rules: [
    { allow: owner, ownerField: "userId" }
  ]) {
  userId: ID!
  posts: [Post] @connection(keyName: "postByUser", fields: ["userId"])
  createdAt: String
  updatedAt: String
}

type Post @model
  @key(name: "postByUser", fields: ["authorId", "createdAt"])
  @auth(rules: [
    { allow: owner, ownerField: "authorId" },
    { allow: public, operations: [read] },
    { allow: private, operations: [read] }
  ]) {
  id: ID!
  authorId: ID!
  author: User @connection(fields: ["authorId"])
  postContent: String
  postImage: String
  comments: [Comment] @connection(keyName: "commentsByPostId", fields: ["id"])
  votes: [PostVote] @connection(keyName: "votesByPostId", fields: ["id"])
  createdAt: String
  voteCount: Int
}

type Comment @model
  @key(name: "commentsByPostId", fields: ["postId"])
  @auth(rules: [
    { allow: owner, ownerField: "authorId" },
    { allow: public, operations: [read] },
    { allow: private, operations: [read] }
  ]) {
  id: ID!
  authorId: ID!
  postId: ID!
  text: String!
  author: User @connection(fields: ["authorId"])
  votes: [CommentVote] @connection(keyName: "votesByCommentId", fields: ["id"])
  post: Post @connection(fields: ["postId"])
  voteCount: Int
}

type PostVote @model
  @auth(rules: [
    { allow: owner, ownerField: "userId"},
    { allow: public, operations: [read] },
    { allow: private, operations: [read] }
  ])
  @key(name: "votesByPostId", fields: ["postId"]) {
  id: ID!
  postId: ID!
  userId: ID!
  post: Post @connection(fields: ["postId"])
  createdAt: String!
  vote: VoteType
}

type CommentVote @model
  @auth(rules: [
    { allow: owner, ownerField: "userId"},
    { allow: public, operations: [read] },
    { allow: private, operations: [read] }
  ])
  @key(name: "votesByCommentId", fields: ["commentId"]) {
  id: ID!
  userId: ID!
  commentId: ID!
  comment: Comment @connection(fields: ["commentId"])
  createdAt: String!
  vote: VoteType
}

input VoteInput {
  type: VoteType!
  id: ID!
}

enum VoteType {
  up
  down
}
```

This implementation will prevent users from voting multiple times, but you must implement a custom resolver to set the vote ID as a combination of the `postId` and the user's `userId`.

The most secure way to do this would be on the server, by reading the user's `userId` from their JWT. To do so, you could first use these two lines of code to set the vote ID to be the unique combination of the `itemId` and the user's ID:

```vtl
#set($itemId = "$context.identity.username#$context.args.postId")
$util.qr($context.args.input.put("id", $util.defaultIfNull($ctx.args.input.id, $itemId)))
```

Then, delete or comment out the conditional expression code that does not allow the vote to be overridden:

```
#set( $condition = {
  "expression": "attribute_not_exists(#id)",
  "expressionNames": {
      "#id": "id"
  }
} )
```

### Vote aggregation

If you would like to implement either a vote count total or some type of algorithmic vote count of your own choosing, you can enable a DynamoDB trigger to invoke a Lambda function where you can write your own custom logic.

[Here](https://github.com/dabit3/dynamodb-triggers/blob/master/counter.js) is an example of a DynamoDB trigger that implements an atomic counter, similar to what you would probably want to do in a situation where you need to be sure votes are incremented or decremented consistently.

## Multi-user Chat App

This app has the following requirements. The User should be able to:

1. Create an account
2. Create a conversation
3. Create a message in a conversation
4. View a list of all conversations
5. Have the ability to create a new conversation with another user

Based on these requirements we can assume we need the following for this application:

1. User, Conversation, and Messsage types
2. Database
3. GraphQL definition for mutations (create, update, delete users, conversations and messages)
4. GraphQL definition for queries
5. GraphQL resolvers for all operations

To build this app, we could use the following annotated GraphQL schema:

```graphql
type User
  @key(fields: ["userId"])
  @model(subscriptions: null)
  @auth(rules: [
    { allow: owner, ownerField: "userId" }
  ]) {
  userId: ID!
  conversations: [ConvoLink] @connection(keyName: "conversationsByUserId", fields: ["userId"])
  messages: [Message] @connection(keyName: "messagesByUserId", fields: ["userId"])
  createdAt: String
  updatedAt: String
}

type Conversation
  @model(subscriptions: null)
  @auth(rules: [{ allow: owner, ownerField: "members" }]) {
  id: ID!
  messages: [Message] @connection(keyName: "messagesByConversationId", fields: ["id"])
  associated: [ConvoLink] @connection(keyName: "convoLinksByConversationId", fields: ["id"])
  members: [String!]!
  createdAt: String
  updatedAt: String
}

type Message
  @key(name: "messagesByConversationId", fields: ["conversationId"])
  @key(name: "messagesByUserId", fields: ["userId"])
  @model(subscriptions: null, queries: null) {
  id: ID!
  userId: ID!
  conversationId: ID!
  author: User @connection(fields: ["userId"])
  content: String!
  conversation: Conversation @connection(fields: ["conversationId"])
  createdAt: String
  updatedAt: String
}

type ConvoLink
  @key(name: "convoLinksByConversationId", fields: ["conversationId"])
  @key(name: "conversationsByUserId", fields: ["userId"])
  @model(
    mutations: { create: "createConvoLink", update: "updateConvoLink" }
    queries: null
    subscriptions: null
  ) {
  id: ID!
  userId: ID!
  conversationId: ID!
  user: User @connection(fields: ["userId"])
  conversation: Conversation @connection(fields: ["conversationId"])
  createdAt: String
  updatedAt: String
}

type Subscription {
  onCreateConvoLink(userId: ID): ConvoLink
    @aws_subscribe(mutations: ["createConvoLink"])
  onCreateMessage(conversationId: ID): Message
    @aws_subscribe(mutations: ["createMessage"])
}
```

## Instagram Clone

This app has the following requirements. The User should be able to:

1. Create an account
2. Create a post
3. Create a comment on a post
4. Follow and unfollow a user
5. Like a comment or a post

Based on these requirements we can assume we need the following for this application:

1. User, Post, Like, Following, and Comment types
2. Database
3. GraphQL definition for mutations (create, update, delete users, posts, comments, following, and likes)
4. GraphQL definition for queries
5. GraphQL resolvers for all operations

To build this app, we could use the following annotated GraphQL schema:

```graphql
type User @model(subscriptions: null)
  @key(fields: ["userId"])
  @auth(rules: [
    { allow: owner, ownerField: "userId" },
    { allow: private, operations: [read] }
    ]) {
  userId: ID!
  posts: [Post] @connection(keyName: "postsByUserId", fields: ["userId"])
  createdAt: String
  updatedAt: String
  following: [Following] @connection(keyName: "followingByUserId", fields: ["userId"])
}

type Post @model
  @key(name: "postsByUserId", fields: ["authorId"])
  @auth(rules: [
    { allow: owner ownerField: "authorId" },
    { allow: public, operations: [read] },
    { allow: private, operations: [read] }
  ]) {
  id: ID!
  authorId: ID!
  content: String!
  postImage: String
  author: User @connection(fields: ["authorId"])
  comments: [Comment] @connection(keyName: "commentsByPostId", fields: ["id"])
  likes: [PostLike] @connection(keyName: "postLikesByPostId", fields: ["id"])
}

type Comment @model
  @key(name: "commentsByPostId", fields: ["postId"])
  @auth(rules: [
    { allow: owner, ownerField: "authorId" },
    { allow: public, operations: [read] },
    { allow: private, operations: [read] }
  ]) {
  id: ID!
  postId: ID!
  authorId: ID!
  text: String!
  likes: [CommentLike] @connection(keyName: "commentLikesByCommentId", fields: ["id"])
  author: User @connection(fields: ["authorId"])
  post: Post @connection(fields: ["postId"])
}

type PostLike @model
  @auth(rules: [
    { allow: owner, ownerField: "userId" },
    { allow: public, operations: [read] },
    { allow: private, operations: [read] }
  ])
  @key(name: "postLikesByPostId", fields: ["postId"])
  @key(name: "postLikesByUser", fields: ["userId", "createdAt"], queryField: "likesByUser") {
  id: ID!
  postId: ID!
  userId: ID!
  user: User @connection(fields: ["userId"])
  post: Post @connection(fields: ["postId"])
  createdAt: String!
}

type CommentLike @model
  @auth(rules: [
    { allow: owner, ownerField: "userId" },
    { allow: public, operations: [read] },
    { allow: private, operations: [read] }
  ])
  @key(name: "commentLikesByCommentId", fields: ["commentId"])
  @key(name: "commentLikesByUser", fields: ["userId", "createdAt"], queryField: "likesByUser") {
  id: ID!
  userId: ID!
  postId: ID!
  commentId: ID!
  user: User @connection(fields: ["userId"])
  post: Post @connection(fields: ["postId"])
  createdAt: String!
}

type Following @model
  @auth(rules: [
    { allow: owner, ownerField: "followerId" },
    { allow: public, operations: [read] },
    { allow: private, operations: [read] }
  ])
  @key(name: "followingByUserId", fields: ["followerId"]) {
  id: ID
  followerId: ID!
  followingId: ID!
  follower: User @connection(fields: ["followerId"])
  following: User @connection(fields: ["followingId"])
  createdAt: String!
}
```

### Likes

Similarly to the Reddit clone, we need to have some custom logic in our resolver to handle likes. To see how this works, check out the custom resolver in the [Reddit Clone](#reddit-clone)

## Conference App

> Click [here](https://github.com/dabit3/conference-app-in-a-box) to view Conference App in a Box, a completed full-stack version of this app built with React Native.

This app has the following requirements. The User should be able to:

1. Create an account
2. View a list of talks
3. View an individual talk
4. Create a comment on a talk
5. (optional) Report a comment

An Admin should be able to:

1. Create, edit, and delete a talk

Based on these requirements we can assume we need the following for this application:

1. Talk, Comment, and (optional) Report types
2. Database
3. GraphQL definition for mutations (create, update, delete talks, comments, and reports)
4. GraphQL definition for queries
5. GraphQL resolvers for all operations

To build this app, we could use the following annotated GraphQL schema:

```graphql
type Talk @model
  @auth(rules: [
    { allow: groups, groups: ["Admin"] },
    { allow: public, operations: [read] },
    { allow: private, operations: [read] }
  ]) {
  id: ID!
  name: String!
  speakerName: String!
  speakerBio: String!
  time: String
  timeStamp: String
  date: String
  location: String
  summary: String!
  twitter: String
  github: String
  speakerAvatar: String
  comments: [Comment] @connection(keyName: "commentsByTalkId", fields: ["id"])
}

type Comment @model
  @key(name: "commentsByTalkId", fields: ["talkId"])
  @auth(rules: [
    { allow: owner, ownerField: "authorId" },
    { allow: public, operations: [read] },
    { allow: private, operations: [read] }
  ])
{
  id: ID!
  talkId: ID!
  talk: Talk @connection(fields: ["talkId"])
  message: String
  createdAt: String
  authorId: ID!
  deviceId: ID
}

type Report @model
  @auth(rules: [
    { allow: owner, operations: [create, update, delete] },
    { allow: groups, groups: ["Admin"] }
  ])
  {
  id: ID!
  commentId: ID!
  comment: String!
  talkTitle: String!
  deviceId: ID
}

type ModelCommentConnection {
  items: [Comment]
  nextToken: String
}

type Query {
  listCommentsByTalkId(talkId: ID!): ModelCommentConnection
}

type Subscription {
  onCreateCommentWithId(talkId: ID!): Comment
		@aws_subscribe(mutations: ["createComment"])
}
```

In this schema, notice we are adding an additional subscription to listen to new comments by ID. This way we can only subscribe to comments for the talk that we are currently viewing.

## Conclusion

![](https://thepracticaldev.s3.amazonaws.com/i/1cqd63ikqphs3swcxzuw.jpg)

> *My Name is [Nader Dabit](https://twitter.com/dabit3). I am a Developer Advocate at Amazon Web Services working with projects like [AWS AppSync](https://aws.amazon.com/appsync/) and [AWS Amplify](https://aws-amplify.github.io/). I specialize in cross-platform & cloud-enabled application development.*