AppSync/Amplify - how to define GraphQL subscriptions - graphql

I am using Amplify to auto-generate queries, mutations and subscriptions. and I have this type:
type Message #model {
id: ID!
author: User #connection(name: "UserMessages")
content: String
}
How do I add authorID as a filter to subscriptions for new messages using Amplify generated schema?

You may add your own subscription fields that are parameterized however you like.
Try this
# Add the authorId explicitly and turn off the generated subscriptions.
type Message #model(subscriptions: null) {
id: ID!
author: User #connection(name: "UserMessages")
authorId: String
content: String
}
type Subscription {
onCreateMessage(authorId: String!): Message #aws_subscribe(mutations: "createMessage")
}
Subscribed clients only get messages with the authorId provided in the subscription query:
subscription SubscribeToNewMessages($id: String!) {
onCreateMessage(authorId: $id) {
id
authorId
content
}
}

Related

AWS AppSync GraphQL subscriptions returning null values on event

New to GraphQL, Amplify, AppSync, etc, and running into an issue when attempting to subscribe to an onUpdate event.
I added the 'API' library to my Amplify project with authentication through an API key. I can successfully send a mutation (updatePurchaseOrder) request through Postman, and the subscription listener registers an update, but the data returned is null on everything besides the id of the updated record.
screenshot in the AppSync console
The status field is null here, I would expect to see the new updated value. Is that the expected behavior?
The defined type on this:
type PurchaseOrder #model #auth(rules: [ { allow: public } ] ){
id: ID!
name: String!
description: String!
user: String
time: String
file: String!
status: String
}
Schema mutations and subscriptions created from the initial type definition haven't been changed:
type PurchaseOrder {
id: ID!
name: String!
description: String!
user: String
time: String
file: String!
status: String
createdAt: AWSDateTime!
updatedAt: AWSDateTime!
}
type Mutation {
updatePurchaseOrder(input: UpdatePurchaseOrderInput!, condition: ModelPurchaseOrderConditionInput): PurchaseOrder
}
input UpdatePurchaseOrderInput {
id: ID!
name: String
description: String
user: String
time: String
file: String
status: String
}
type Subscription {
onUpdatePurchaseOrder: PurchaseOrder
#aws_subscribe(mutations: ["updatePurchaseOrder"])
}
I tried logging to console in browser and see the same empty object returned. Figured listening through the AppSyncConsole would be less error prone, but I'm still seeing the same result.
Figured out where I went wrong... I needed to specify the response fields I wanted in the POST query (eg.:
mutation updatePurchaseOrder {
updatePurchaseOrder(input: {status: "Pending", id: "53a98236-7b64-428c-93ea-2fae5228c0ef"}) {
id
**status**
}
)
'status' was not in the query response parameters originally. The subscription response will only include fields that are part of the mutation response in the query itself.

Apollo GraphiQL browser playground: How to nest queries?

Wish to run a nested query within as Apollo GraphiQL browser playground.
Schema for two queries intended to be nested and a response type:
getByMaxResults(maxResults: Int = null): [ID!]!
appById(id: ID!): AppOutputGraphType
AppOutputGraphType = {
accountIdGuid: ID!
createdAt: DateTime!
description: String!
id: ID!
updatedAt: DateTime!
userId: ID
}
Query getByMaxResults: [ID!]!, the result is an array of element ID:
Query appById(id: ID!) requesting a couple of AppOutputGraphType fields:
Is there a way within the GraphiQL browser sandbox to nest these two Queries?:
query getByMaxResults gather an array of IDs.
Iterate this array and perform multiple queries appById with each ID.
Thank you
Change your definition of the getByMaxResults query to:
getByMaxResults: [AppOutputGraphType!]!
Change your resolver to return an array containing all the fields of the AppOutputGraphType objects and not just the id.
Then just:
query {
getByMaxResults {
id
updatedAt
}
}
There is no way in GraphQL to nest queries but the whole point of the language is to remove the need to do so.
With the proper resolvers you could even get the related account and user for each AppOutputGraphType.
Change:
AppOutputGraphType = {
account: Account <= don't include foreign keys, refer to the related type
createdAt: DateTime!
description: String!
id: ID!
updatedAt: DateTime!
user: User <= don't include foreign keys, refer to the related type
}
Then you can do queries like:
query {
getByMaxResults {
id
updatedAt
account {{
id
name
city
}
user {
id
firstName
lastName
}
}
}

Apollo and Prisma Graphql explicit model relationship not queryable

I have begun testing out prisma 2 and graphql in general for a new application. I am running into an issue with an explicit many to many table on being able to query relations.
Here is my apollo schema:
scalar DateTime
type Query {
user(id: String!): User
users: [User]
spaces: [Space]
roles: [Role]
}
type Mutation {
createUser(id: String!, email: String!): User!
createSpace(name: String!): Space!
}
type User {
id: ID!
email: String!
spaces: [UserSpace!]
createdAt: DateTime!
updatedAt: DateTime!
}
type Space {
id: ID!
name: String!
users: [UserSpace!]
createdAt: DateTime!
updatedAt: DateTime!
}
type Role {
id: ID!
name: String!
description: String!
users: UserSpace
createdAt: DateTime!
updatedAt: DateTime!
}
type UserSpace {
id: ID!
user: User!
space: Space!
role: Role!
createdAt: DateTime!
updatedAt: DateTime!
}
Here is my prisma schema:
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// npx prisma migrate dev
// npx prisma generate
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id String #id
email String #unique
spaces UserSpace[]
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
}
model Space {
id Int #default(autoincrement()) #id
name String #unique
users UserSpace[]
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
}
model Role {
id Int #default(autoincrement()) #id
name String #unique
description String
users UserSpace[]
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
}
model UserSpace {
id Int #default(autoincrement()) #id
user User #relation(fields: [userId], references: [id])
userId String
space Space #relation(fields: [spaceId], references: [id])
spaceId Int
role Role #relation(fields: [roleId], references: [id])
roleId Int
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
}
Here is my mutations resolver:
const { prisma } = require(".prisma/client");
async function createUser(parent, args, context, info) {
return await context.prisma.user.create({
data: {
...args,
},
});
}
async function createSpace(parent, args, context, info) {
const isAuthenticated = context.authentication.isAuthenticated;
let role = null;
if(!isAuthenticated) {
throw new Error("Not Authenticated");
}
try {
role = await context.prisma.role.findUnique({
where: {
name: "Space Administrator",
},
});
}
catch(err) {
throw new Error(err);
}
return await context.prisma.space.create({
data: {
...args,
users: {
create: {
role: {
connect: { id: role.id },
},
user: {
connect: { id: context.authentication.auth0Id },
},
},
},
},
});
}
module.exports = {
createUser,
createSpace,
}
Here is my user resolver (I know this is where the problem is however I do not know how to solve the issue):
function spaces(parent, args, context, info) {
return context.prisma.user.findUnique({ where: { id: parent.id } }).spaces();
}
module.exports = {
spaces,
}
Basically when I create the space the user is added as a Space Administrator to the space and then should be able to be queried with the following:
query {
users {
id
email
spaces {
id
role {
name
}
space {
name
}
}
createdAt
}
}
However when I run the query I get the following error:
"message": "Cannot return null for non-nullable field UserSpace.role.",
How in prisma 2 do I make the resolver for the users work with an explicit many to many table and how it has the third relation in there? I am new to prisma and graphql so if there anything else that stands out also I would like to have the input.
I'm using the word type to refer to object-models in your GraphQL schema and model to refer to data-models in your Prisma Schema.
The Problem
I see that you have a User type resolver, that has a resolver function for User.spaces field in your User type. The query that you have defined in your User.spaces resolver will return the relevant userSpace records from the database.
However, these userSpace records do not by default resolve the role field, as it is a relation field. This is how prisma works (relation fields are not resolved by default, unless explicitly stated).
Solution
Create a resolver for the UserSpace type and explicitly define the the resolver function for UserSpace.role field. This is what it will look like
// UserSpace resolver module
function role(parent, args, context, info) {
return context.prisma.userSpace.findUnique({ where: { id: parent.id } }).role();
}
module.exports = {
role,
}
While there are some other ways to solve this problem, the way I have shown (along with the specific syntax) is recommended because under the hood it allows prisma to perform certain optimizations to solve the n+1 query problem. But, if you don't know what that is, you don't necessarily need to worry about it either.
Did you provide the value to users arg? like this: users(id: "string_value"). Because is id is required.

Using nested arguments in GraphQL operations

I have a schema like so:
scalar Date
schema {
query: Query
}
type Query {
user(id: ID!): User
messages(userId: ID!): [ChatMessage!]!
}
type User {
id: ID!
username: String!
email: String!
}
type ChatMessage {
id: ID!
content: String!
time: Date!
user: User!
}
And I want to make an operation where you can get all messages for a user, but since User and ChatMessage are in separate database tables I would need to perform two queries (one to get the ChatMessages and one to get the User), so I thought I should model it like so:
query findMessagesForUser($userId: ID!) {
messages(userId: $userId) {
id
content
user(id: $userId) {
username
email
}
}
}
This returns a parse error on the schema:
GraphQLDocumentError: Unknown argument "id" on field "ChatMessage.user".
So, how do I get the $userId argument passed to the resolver for ChatMessage.user?
In your schema, you’ve defined an id input on your Query.user method. In your query, you are trying to supply an id to the Message.user property, however you haven't defined this input in your schema.
If you wanted to accept an id on ChatMessage.user, you'd need to define it as:
type ChatMessage {
id: ID!
content: String!
time: Date!
user(id: ID!): User
}
However, it wouldn't really make sense (at least to me) to construct a schema this way, I assume there's only one user (author) per message.
As indicated by #xadm, the object you resolved at the ChatMessage level will be passed into the user resolver as the first argument.
Even if you're not exposing ChatMessage.userId in the schema (that's fine), you'd still probably load this up in your back- end (the foreign key value in the ChatMessage table) and set this on the object used to resolve ChatMessage.
This way, you'll (lazy) load user IF that's included in the query, using the userId property of the parent ChatMessage object argument (remember you don't need to expose ChatMessage.userId via the schema, it's just on the object you use to resolve ChatMessage).
I'd consider modelling more like this (filter input used as an additional contrived example):
type Query {
user(id: ID!): User
messages(filter: MessageFilter): [ChatMessage!]!
}
type MessageFilter {
search: String
paging: PagingFilter
}
type PagingFilter {
after: ID!
pageSize: Int!
}
type User {
id: ID!
username: String!
email: String!
messages(filter: MessageFilter): [ChatMessage!]!
}
In your resolver map, you can wire up the same function to resolve messages at the User level and at the Query level. The only difference is you wouldn't have a userId at the Query level.
If consumers want to view/search messages from all users, they use the top level Query messages method.
{
messages({search: 'graphql'}) {
id,
content,
time
}
}
If the consumer wants to view/search one user's messages, go through the top level Query users method into messages.
{
user(id: 3) {
messages({search: 'graphql'}) {
id,
content,
time
}
}
}
The filter example is contrived, but could provide basic paging for loading of messages.
apollographql.com/docs/graphql-tools/resolvers

AppSync subscription authorization problem

Hi i am new in aws AppSync and GraphQl. I have a problem with subscription. I want to get notifiend in real time when new post is posted
Here is my graphql schema ``
type Mutation {
addPost(
id: ID!,
author: String!,
title: String,
contennt: String,
url: String
): Post!
updatePost(
id: ID!,
author: String!,
title: String,
content: String,
ups: Int!
): Post!
deletePost(id: ID): Post!
}
type Post {
id: ID!
author: String!
title: String!
url: String
content: String
ups: Int!
}
type Query {
allPost: [Post]
getPost(id: ID!): Post
}
type Subscription {
newPost: Post
#aws_subscribe(mutations: ["addPost"])
}
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
``
Here is my Query for subs:
subscription NewPostSub{
newPost{
__typename
title
content
author
url
}
}
Im getting error that provided key element doesnt match a schema, in dynamodb table pk is id and i generated mapping template.
Thank you
Thanks for the info.
First, you do not need a resolver on your subscription field at all. The only reason you need to add a resolver to a subscription field is to do authorization checks based on the calling identity at connect time. You do not need to add a resolver to make subscriptions work. To fix it, remove the resolver off the subscription (Subscription.newPost) field.
To solve your use case and make it so you only subscribe to posts with a certain id, change your newPost subscription field to newPost(id: ID!). When you make the subscription query, provide an id and that subscription will only get pushed posts returned by the subscribed to mutation with that id. This is a feature baked into AppSync and the argument equality check happens automatically when you add the arguments to the subscription field.
Secondly as you pasted it in the comment, you currently have this request mapping template for Mutation.addPost:
{
"version" : "2017-02-28",
"operation" : "PutItem",
"key" : {
$util.dynamodb.toDynamoDBJson($ctx.args.id)
"id": $util.dynamodb.toDynamoDBJson($util.autoId()),
},
"attributeValues" : $util.dynamodb.toMapValuesJson($ctx.args)
}
There is a typo and it should be this:
{
"version" : "2017-02-28",
"operation" : "PutItem",
"key" : {
"id": $util.dynamodb.toDynamoDBJson($util.autoId()),
},
"attributeValues" : $util.dynamodb.toMapValuesJson($ctx.args)
}

Resources