{"id":13532572,"url":"https://github.com/dabit3/heard","last_synced_at":"2025-11-17T15:09:03.174Z","repository":{"id":73191052,"uuid":"133583975","full_name":"dabit3/heard","owner":"dabit3","description":"React Native Enterprise Social Messaging App","archived":false,"fork":false,"pushed_at":"2018-07-01T16:16:59.000Z","size":1494,"stargazers_count":239,"open_issues_count":5,"forks_count":37,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-11-01T04:06:42.595Z","etag":null,"topics":["appsync","aws","aws-amplify","aws-appsync","mobx","react","react-native"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dabit3.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2018-05-15T23:27:14.000Z","updated_at":"2024-10-27T14:21:49.000Z","dependencies_parsed_at":null,"dependency_job_id":"714b8d1d-3ae0-44c4-8e60-0320bcac8635","html_url":"https://github.com/dabit3/heard","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dabit3/heard","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dabit3%2Fheard","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dabit3%2Fheard/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dabit3%2Fheard/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dabit3%2Fheard/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dabit3","download_url":"https://codeload.github.com/dabit3/heard/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dabit3%2Fheard/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":284903337,"owners_count":27082026,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-11-17T02:00:06.431Z","response_time":55,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["appsync","aws","aws-amplify","aws-appsync","mobx","react","react-native"],"created_at":"2024-08-01T07:01:11.977Z","updated_at":"2025-11-17T15:09:03.158Z","avatar_url":"https://github.com/dabit3.png","language":"JavaScript","readme":"# Heard - An enterprise React Native Social Messaging App\n\n### Built with AWS AppSync \u0026 AWS Amplify\n\n![](https://imgur.com/Kqmdwdy.jpg)\n\n### Todo\n\n- [ ] Add subscriptions for real time updates / messages in feed\n- [ ] Add user profile section\n- [ ] Add \"follower\" tab\n\n# Getting Started\n\n## Cloning the project \u0026 creating the services\n\n1. Clone the project\n\n```bash\ngit clone \n```\n\n2. Install dependencies\n\n```bash\nyarn\n# or\nnpm install\n```\n\n3. Create new AWS Mobile Project\n\n```bash\nawsmobile init\n```\n\n4. Add Authentication service\n\n```bash\nawsmobile user-signin enable\n```\n\n5. Push configuration to AWS Mobile Hub\n\n```\nawsmobile push\n```\n\n## Configuring the AWS AppSync API\n\n1. Create \u0026 configure a new AppSync API\n\n- Visit the [AWS AppSync](https://console.aws.amazon.com/appsync/home) console and create a new API.\n- In Settings, set the Auth mode to Amazon Cognito User Pool and choose the user pool created in the initial service creation.\n\n2. In index.js on line 11, change `\u003cYOURAPPSYNCENDPOINT\u003e` to the endpoint given to you when you created the AppSync API.\n\n3. Attach the following Schema:\n\n```graphql\ninput CreateFollowingInput {\n\tid: ID\n\tfollowerId: ID!\n\tfollowingId: ID!\n}\n\ninput CreateMessageInput {\n\tmessageId: ID\n\tauthorId: ID!\n\tcreatedAt: String\n\tmessageInfo: MessageInfoInput!\n\tauthor: UserInput\n}\n\ninput CreateUserInput {\n\tuserId: ID!\n\tusername: String!\n}\n\ninput DeleteFollowingInput {\n\tid: ID!\n}\n\ninput DeleteMessageInput {\n\tauthorId: ID!\n\tcreatedAt: String!\n}\n\ninput DeleteUserInput {\n\tuserId: ID!\n}\n\ntype Following {\n\tid: ID\n\tfollowerId: ID!\n\tfollowingId: ID!\n}\n\ntype FollowingConnection {\n\titems: [Following]\n\tnextToken: String\n}\n\ntype ListUserConnection {\n\titems: [User]\n\tnextToken: String\n}\n\ntype Mutation {\n\tcreateMessage(input: CreateMessageInput!): Message\n\tupdateMessage(input: UpdateMessageInput!): Message\n\tdeleteMessage(input: DeleteMessageInput!): Message\n\tcreateUser(input: CreateUserInput!): User\n\tupdateUser(input: UpdateUserInput!): User\n\tdeleteUser(input: DeleteUserInput!): User\n\tcreateFollowing(input: CreateFollowingInput!): Following\n\tupdateFollowing(input: UpdateFollowingInput!): Following\n\tdeleteFollowing(input: DeleteFollowingInput!): Following\n}\n\ntype Query {\n\tgetMessage(authorId: ID!, createdAt: String!): Message\n\tlistMessages(first: Int, after: String): MessageConnection\n\tlistFollowing: [Following]\n\tgetUser(userId: ID!): User\n\tlistUsers(first: Int, after: String): ListUserConnection\n\tqueryMessagesByAuthorIdIndex(authorId: ID!, first: Int, after: String): MessageConnection\n}\n\ntype Subscription {\n\tonCreateMessage(messageId: ID, authorId: ID, createdAt: String): Message\n\t\t@aws_subscribe(mutations: [\"createMessage\"])\n\tonUpdateMessage(messageId: ID, authorId: ID, createdAt: String): Message\n\t\t@aws_subscribe(mutations: [\"updateMessage\"])\n\tonDeleteMessage(messageId: ID, authorId: ID, createdAt: String): Message\n\t\t@aws_subscribe(mutations: [\"deleteMessage\"])\n\tonCreateUser(userId: ID, username: String): User\n\t\t@aws_subscribe(mutations: [\"createUser\"])\n\tonUpdateUser(userId: ID, username: String): User\n\t\t@aws_subscribe(mutations: [\"updateUser\"])\n\tonDeleteUser(userId: ID, username: String): User\n\t\t@aws_subscribe(mutations: [\"deleteUser\"])\n\tonCreateFollowing(id: ID, followerId: ID, followingId: ID): Following\n\t\t@aws_subscribe(mutations: [\"createFollowing\"])\n\tonUpdateFollowing(id: ID, followerId: ID, followingId: ID): Following\n\t\t@aws_subscribe(mutations: [\"updateFollowing\"])\n\tonDeleteFollowing(id: ID, followerId: ID, followingId: ID): Following\n\t\t@aws_subscribe(mutations: [\"deleteFollowing\"])\n}\n\ntype Message {\n\tmessageId: ID!\n\tauthorId: ID!\n\tmessageInfo: MessageInfo!\n\tauthor: User\n\tcreatedAt: String\n}\n\ntype MessageConnection {\n\titems: [Message]\n\tnextToken: String\n}\n\ntype MessageInfo {\n\ttext: String!\n}\n\ninput MessageInfoInput {\n\ttext: String!\n}\n\ninput UpdateFollowingInput {\n\tid: ID!\n\tfollowerId: ID\n\tfollowingId: ID\n}\n\ninput UpdateMessageInput {\n\tmessageId: ID\n\tauthorId: ID!\n\tcreatedAt: String!\n}\n\ninput UpdateUserInput {\n\tuserId: ID!\n\tusername: String\n}\n\ntype User {\n\tuserId: ID!\n\tusername: String\n\tmessages(limit: Int, nextToken: String): MessageConnection\n\tfollowing(limit: Int, nextToken: String): UserFollowingConnection\n\tfollowers(limit: Int, nextToken: String): UserFollowersConnection\n}\n\ntype UserConnection {\n\titems: [User]\n\tnextToken: String\n}\n\ntype UserFollowersConnection {\n\titems: [User]\n\tnextToken: String\n}\n\ntype UserFollowingConnection {\n\titems: [User]\n\tnextToken: String\n}\n\ninput UserInput {\n\tuserId: ID!\n\tusername: String!\n}\n```\n\n4. Create the following DynamoDB Tables\n\n- HeardMessageTable\n- HeardFollowingTable\n- HeardUserTable\n\n5. Add the following indexes:\n\n- In HeardMessageTable, create an `authorId-index` with the `authorId` as the primary / partition key.\n- In HeardFollowingTable, create a `followingId-index` with the `followingId` as the primary / partition key.\n- In HeardFollowingTable, create a `followerId-index` with the `followerId` as the primary / partition key.\n\n__To create an index, click on the table you would like to create an index on, click on the `indexes` tab, then click _Create Index_ .__\n\n6. Create the following resolvers:\n\n#### Message author: User: HeardUserTable\n\n```js\n// request mapping template\n{\n    \"version\": \"2017-02-28\",\n    \"operation\": \"GetItem\",\n    \"key\": {\n        \"userId\": $util.dynamodb.toDynamoDBJson($ctx.source.authorId),\n    }\n}\n\n// response mapping template\n$util.toJson($ctx.result)\n```\n\n#### ListUserConnection items: [User]: HeardUserTable\n\n```js\n// request mapping template\n{\n    \"version\" : \"2017-02-28\",\n    \"operation\" : \"Scan\",\n}\n\n// response mapping template\n$util.toJson($ctx.result.items)\n```\n\n#### Query getUser(...): User: HeardUserTable\n\n```js\n// request mapping template\n{\n  \"version\": \"2017-02-28\",\n  \"operation\": \"GetItem\",\n  \"key\": {\n    \"userId\": $util.dynamodb.toDynamoDBJson($ctx.args.userId),\n  },\n}\n\n// response mapping template\n$util.toJson($context.result)\n```\n\n#### Query listUsers(...): ListUserConnection: HeardUserTable\n\n```js\n// request mapping template\n{\n    \"version\" : \"2017-02-28\",\n    \"operation\" : \"Scan\",\n    \"limit\": $util.defaultIfNull(${ctx.args.limit}, 20),\n    \"nextToken\": $util.toJson($util.defaultIfNullOrBlank($ctx.args.nextToken, null))\n}\n\n// response mapping template\n$util.toJson($ctx.result.items)\n```\n\n#### Query listFollowing: [Following]: HeardFollowingTable\n\n```js\n// request mapping template\n{\n    \"version\" : \"2017-02-28\",\n    \"operation\" : \"Query\",\n    \"index\" : \"followerId-index\",\n    \"query\" : {\n        \"expression\": \"followerId = :id\",\n        \"expressionValues\" : {\n            \":id\" : {\n                \"S\" : \"${ctx.identity.sub}\"\n            }\n        }\n    }\n}\n\n// response mapping template\n$util.toJson($ctx.result.items)\n```\n\n#### Query queryMessagesByAuthorIdIndex(...): MessageConnection: HeardMessageTable\n\n```js\n// request mapping template\n{\n  \"version\": \"2017-02-28\",\n  \"operation\": \"Query\",\n  \"query\": {\n    \"expression\": \"#authorId = :authorId\",\n    \"expressionNames\": {\n      \"#authorId\": \"authorId\",\n    },\n    \"expressionValues\": {\n      \":authorId\": $util.dynamodb.toDynamoDBJson($ctx.args.authorId),\n    },\n  },\n  \"index\": \"authorId-index\",\n  \"limit\": $util.defaultIfNull($ctx.args.first, 20),\n  \"nextToken\": $util.toJson($util.defaultIfNullOrEmpty($ctx.args.after, null)),\n  \"scanIndexForward\": true,\n  \"select\": \"ALL_ATTRIBUTES\",\n}\n\n// response mapping template\n$util.toJson($context.result)\n```\n\n#### Mutation createFollowing(...): Following: HeardFollowingTable\n\n```js\n// request mapping template\n{\n  \"version\": \"2017-02-28\",\n  \"operation\": \"PutItem\",\n  \"key\": {\n     ## If object \"id\" should come from GraphQL arguments, change to $util.dynamodb.toDynamoDBJson($ctx.args.id)\n    \"id\": $util.dynamodb.toDynamoDBJson($util.autoId()),\n  },\n  \"attributeValues\": $util.dynamodb.toMapValuesJson($ctx.args.input),\n  \"condition\": {\n    \"expression\": \"attribute_not_exists(#id)\",\n    \"expressionNames\": {\n      \"#id\": \"id\",\n    },\n  },\n}\n\n// response mapping template\n$util.toJson($context.result)\n```\n\n#### Mutation deleteFollowing(...): Following: HeardFollowingTable\n\n```js\n// request mapping template\n{\n  \"version\": \"2017-02-28\",\n  \"operation\": \"DeleteItem\",\n  \"key\": {\n    \"id\": $util.dynamodb.toDynamoDBJson($ctx.args.input.id),\n  },\n}\n\n// response mapping template\n$util.toJson($context.result)\n```\n\n#### Mutation createUser(...): User: HeardUserTable\n\n```js\n// request mapping template\n{\n  \"version\": \"2017-02-28\",\n  \"operation\": \"PutItem\",\n  \"key\": {\n    \"userId\": $util.dynamodb.toDynamoDBJson($ctx.args.input.userId),\n  },\n  \"attributeValues\": $util.dynamodb.toMapValuesJson($ctx.args.input),\n  \"condition\": {\n    \"expression\": \"attribute_not_exists(#userId)\",\n    \"expressionNames\": {\n      \"#userId\": \"userId\",\n    },\n  },\n}\n\n// response mapping template\n$util.toJson($context.result)\n```\n\n#### Mutation createMessage(...): Message: HeardMessageTable\n\n```js\n// request mapping template\n#set($time = $util.time.nowISO8601())\n\n#set($attribs = $util.dynamodb.toMapValues($ctx.args.input))\n#set($attribs.createdAt = $util.dynamodb.toDynamoDB($time))\n#set($attribs.messageId = $util.dynamodb.toDynamoDB($util.autoId()))\n\n{\n  \"version\": \"2017-02-28\",\n  \"operation\": \"PutItem\",\n  \"key\": {\n    \"authorId\": $util.dynamodb.toDynamoDBJson($ctx.args.input.authorId),\n    \"createdAt\": $util.dynamodb.toDynamoDBJson($time),\n  },\n  \"attributeValues\": $util.toJson($attribs),\n  \"condition\": {\n    \"expression\": \"attribute_not_exists(#authorId) AND attribute_not_exists(#createdAt)\",\n    \"expressionNames\": {\n      \"#authorId\": \"authorId\",\n      \"#createdAt\": \"createdAt\",\n    },\n  },\n}\n\n// response mapping template\n$util.toJson($context.result)\n```\n\n#### User messages(...): MessageConnection: HeardMessageTable\n\n```js\n// request mapping template\n{\n    \"version\" : \"2017-02-28\",\n    \"operation\" : \"Query\",\n    \"index\" : \"authorId-index\",\n    \"query\" : {\n        \"expression\": \"authorId = :id\",\n        \"expressionValues\" : {\n            \":id\" : {\n                \"S\" : \"${ctx.source.userId}\"\n            }\n        }\n    },\n    \"limit\": $util.defaultIfNull(${ctx.args.first}, 20),\n    \"nextToken\": $util.toJson($util.defaultIfNullOrBlank($ctx.args.after, null))\n}\n\n// response mapping template\n$util.toJson($ctx.result)\n```\n\n#### User following(...): UserFollowingConnection: HeardFollowingTable\n\n```js\n// request mapping template\n{\n    \"version\" : \"2017-02-28\",\n    \"operation\" : \"Query\",\n    \"index\" : \"followerId-index\",\n    \"query\" : {\n        \"expression\": \"followerId = :id\",\n        \"expressionValues\" : {\n            \":id\" : {\n                \"S\" : \"${ctx.source.userId}\"\n            }\n        }\n    }\n    ## ,\n    ## \"limit\": $util.defaultIfNull(${ctx.args.first}, 20),\n    ## \"nextToken\": $util.toJson($util.defaultIfNullOrBlank($ctx.args.after, null))\n}\n\n// response mapping template\n## Pass back the result from DynamoDB. **\n## $util.qr($util.error($ctx.result))\n$util.toJson($ctx.result)\n```\n\n#### UserFollowingConnection items: [User]: HeardUserTable\n\n```js\n// request mapping template\n## UserFollowingConnection.items.request.vtl **\n \n#set($ids = [])\n#foreach($following in ${ctx.source.items})\n    #set($map = {})\n    $util.qr($map.put(\"userId\", $util.dynamodb.toString($following.get(\"followerId\"))))\n    $util.qr($ids.add($map))\n#end\n \n{\n    \"version\" : \"2018-05-29\",\n    \"operation\" : \"BatchGetItem\",\n    \"tables\" : {\n        \"HeardUserTable\": {\n           \"keys\": $util.toJson($ids),\n           \"consistentRead\": true\n       }\n    }\n}\n\n// response mapping template\n## Pass back the result from DynamoDB. **\n#if( ! ${ctx.result.data} )\n  $util.toJson([])\n#else\n  $util.toJson($ctx.result.data.HeardUserTable)\n#end\n\n## $util.toJson($ctx.result.data.HeardUserTable)\n```","funding_links":[],"categories":["Example Projects","JavaScript"],"sub_categories":["Other blogs \u0026 tutorials"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdabit3%2Fheard","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdabit3%2Fheard","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdabit3%2Fheard/lists"}