Relationships with AwsCdk, DynamoDB and AppSync - Typescript and lambda functions - graphql

we are currently studying the stack: cdk, appsync and amplify to migrate our applications.
In our initial tests, we were able to upload a graphql api with only appsync wit relationships and it was very smooth, nice and fast.
When testing to build with cdk, we are having difficulties to create the relationships.
Here my code:
Schema
type Person {
id: ID!
name: String!
}
input PersonInput {
id: ID!
name: String!
}
input UpdatePersonInput {
id: ID!
name: String
}
type Client {
id: ID!
type: String!
personId: String
# Person: PersonConnection
Person: Person #connection(fields: ["personId"])
}
input ClientInput {
id: ID!
type: String!
personId: String!
}
input UpdateClientInput {
id: ID!
type: String
personId: String
}
My function
const AWS = require('aws-sdk');
const docClient = new AWS.DynamoDB.DocumentClient();
async function list() {
const params = {
TableName: process.env.CLIENT_TABLE,
}
try {
const data = await docClient.scan(params).promise()
return data.Items
} catch (err) {
console.log('DynamoDB error: ', err)
return null
}
}
export default list;
My table
const clientTable = new dynamodb.Table(scope, 'ClientTable', {
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
partitionKey: {
name: 'id',
type: dynamodb.AttributeType.STRING,
},
});
clientTable.addGlobalSecondaryIndex({
indexName: 'client-by-person-id',
partitionKey: {
name: 'personId',
type: dynamodb.AttributeType.STRING
},
sortKey: {
name: 'createdAt',
type: dynamodb.AttributeType.STRING
}
})
My query
query MyQuery {
listClients {
id
personId
type
Person {
name
}
}
}
However, my return to Person connection is null
"listClients": [
{
"id": "1",
"personId": "1",
"type": "PJ",
"Person": null
}
]
I would appreciate it if could point out our mistake

Solution of the problem based on the response of the Thorsten.
First, add resolver to the Person field in Client
export const clientResolvers = [{ typeName: "Client", fieldName: "Person" },...]
clientResolvers.map(((resolver: clientTypeResolver) => dataSource2.createResolver(resolver)))
Map function to the Person field in its lambda function
type AppSyncEvent = {
...
source: {personId: string,}
...
}
exports.handler = async (event:AppSyncEvent) => {
switch (event.info.fieldName) {
...
case "Person":
return await getPerson(event.source.personId);
}
}```
Function to solve the person field
async function getPerson(personId: string) {
console.log("CONTEXT\n" + JSON.stringify(personId, null, 2))
// console.log(context.source)
const params = {
TableName: process.env.PERSON_TABLE,
Key: { id: personId }
}
try {
const { Item } = await docClient.get(params).promise()
console.log("DATA\n" + JSON.stringify(Item, null, 2))
return Item
} catch (err) {
console.log('DynamoDB error: ', err)
}

Related

How to add item into an list field?

I want to add members to chatRoom member list by passing memberId to graphql mutation function. But it pop up some errors. I have attached most code that relevant to this question as below. Please help me to figure it out. I guess create: {connect } might be the cause of this issue.
//Here is Mutation function
async function addMemberToChatRoom(parent, args, context, info) {
const member = await context.prisma.users({
where: {
id: args.memberId
}
});
const chatRoom = await context.prisma.updateChatRoom({
where: {
id: args.roomId
},
data: {
users: {
create: {
{ connect: { id: args.memberId } }
}
}
}
})
return chatRoom
}
//Here is prisma datamodel
type User {
id: ID! #id
name: String!
email: String! #unique
password: String!
}
type ChatRoom {
id: ID! #id
name: String!
users: [User]!
}
type Message {
id: ID! #id
content: String!
createdAt: DateTime! #createdAt
updatedAt: DateTime! #updatedAt
}
//Here is Schema.graphql
type Query {
info: String!
users: [User!]!
}
type Mutation {
signup(email: String!, password: String!, name: String!): AuthPayload
login(email: String!, password: String!): AuthPayload
createChatRoom(name: String!): ChatRoom
addMemberToChatRoom(roomId: String!, memberId: String!): ChatRoom
}
type AuthPayload {
token: String!
user: User!
}
type User {
id: ID!
name: String!
email: String!
}
type ChatRoom {
id: ID!
name: String!
users: [User!]
}
//Here is index.js
const { GraphQLServer } = require('graphql-yoga')
const { prisma } = require('./generated/prisma-client')
const Query = require('./resolvers/Query')
const Mutation = require('./resolvers/Mutation')
// const User = require('./resolvers/User')
const resolvers = {
Query,
Mutation
}
const server = new GraphQLServer({
typeDefs: './src/schema.graphql',
resolvers,
context: request => {
return {
...request,
prisma,
}
},
tracing: true,
})
server.start(() => console.log(`Server is running on http://localhost:4000`))
//Here is error
{
"data": {
"addMemberToChatRoom": null
},
"errors": [
{
"message": "Variable '$data' expected value of type 'ChatRoomUpdateInput!' but got: {\"users\":{\"create\":{\"id\":\"cjuzcf7lx75g60b953w50uwdc\"}}}. Reason: 'users.create[0].name' Expected non-null value, found null. (line 1, column 46):\nmutation ($where: ChatRoomWhereUniqueInput!, $data: ChatRoomUpdateInput!) {\n ^",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"addMemberToChatRoom"
]
}
]
}
I remove create and change it to
users:
{
connect: { id: args.memberId }
}
then it works.
async function addMemberToChatRoom(parent, args, context, info) {
const member = await context.prisma.users({
where: {
id: args.memberId
}
});
const chatRoom = await context.prisma.updateChatRoom({
where: {
id: args.roomId
},
data: {
users: {
connect: { id: args.memberId }
}
}
})
const returnedChatRoom = await context.prisma.chatRooms({
where: {
id: args.roomId
}
});
return returnedChatRoom
}

Pass argument child type graphql

I would like to know how can I pass an argument in "child" type.
With the request, I would like to have only the message with id 1 from user id 1.
Currently I have this request :
{user(id:1){
email,
username,
messages(id:1){
text,
id
}
}}
I modified in the schema the User type, and changed
messages: [Message]
by
messages(id: Int): [Message]
But I have always all messages from user and not the message with specific id.
schema.js
import { gql } from 'apollo-server';
export const typeDefs = gql`
# declare custom scalars
scalar Date
##########
## TYPE ##
##########
# a group
type Group {
id: Int!
name: String
users: [User]!
messages: [Message]
}
# a user
type User {
id: Int! # unique id for the user
email: String!
username: String
messages(id: Int): [Message]
groups: [Group]
friends: [User]
}
# a message sent from a user to a group
type Message {
id: Int!
to: Group!
from: User!
text: String!
createdAt: Date!
}
###########
## QUERY ##
###########
# query for types
type Query {
user(email: String, id: Int): User
messages(groupId: Int, userId: Int): [Message]
}
schema {
query: Query
}
`;
export default typeDefs;
resolvers.js
import GraphQLDate from 'graphql-date';
import { Group, Message, User } from './connectors';
export const resolvers = {
Date: GraphQLDate,
Query: {
group(_, args) {
return Group.find({ where: args });
},
messages(_, args) {
return Message.findAll({
where: args,
order: [['createdAt', 'DESC']],
});
},
user(_, args) {
return User.findOne({ where: args });
},
},
Group: {
users(group) {
return group.getUsers();
},
messages(group) {
return Message.findAll({
where: { groupId: group.id },
order: [['createdAt', 'DESC']],
});
},
},
Message: {
to(message) {
return message.getGroup();
},
from(message) {
return message.getUser();
},
},
User: {
messages(user) {
return Message.findAll({
where: { userId: user.id },
order: [['createdAt', 'DESC']],
});
},
groups(user) {
return user.getGroups();
},
friends(user) {
return user.getFriends();
},
},
};
export default resolvers;
You have to update User.messages resolver. This way:
User: {
messages(user, { id }) {
return Message.findAll({
where: { userId: user.id, id },
order: [['createdAt', 'DESC']],
});
},
groups(user) {
return user.getGroups();
},
friends(user) {
return user.getFriends();
},
},
If you want to make id parameter optional, something like this may help:
messages(user, { id }) {
const where = { userId: user.id }
if (id) {
where.id = id
}
return Message.findAll({
where,
order: [['createdAt', 'DESC']],
});
},

apollo-server-express: how to resolve inside subscriptions?

When doing regular graphQL queries, I have no trouble to get nested objects or computations. But when inside a subscription like:
subscription {
Event(filter: {
mutation_in: [CREATED]
}) {
node {
title
description
start
end
seats
bookings
owner {
email
}
}
}
}
It breaks as soon as I add bookings or owner { ... }. I see no error message on then server. QraphiQL only displays a [object Object] as a response.
The interesing par of the schema is:
type Event {
id: ID!
title: String!
description: String!
owner: User!
seats: Int
bookings: Int
start: DateTime!
end: DateTime!
tickets: [EventTicket!]!
}
type Mutation {
createEvent(event: EventInput!): Event
}
input EventInput {
title: String!
description: String!
seats: Int
start: DateTime!
end: DateTime!
}
type Subscription {
Event(filter: EventSubscriptionFilter): EventSubscriptionPayload
}
input EventSubscriptionFilter {
mutation_in: [_ModelMutationType!]
}
type EventSubscriptionPayload {
mutation: _ModelMutationType!
node: Event
}
enum _ModelMutationType {
CREATED
UPDATED
DELETED
}
And the resolvers:
import { ObjectId } from "mongodb"
import pubsub from "../../utils/pubsub"
export default {
// ...
Mutation: {
createEvent: async (_, data, { mongo: { Events }, user }) => {
const newEvent = data.event
newEvent.ownerId = user._id
const response = await Events.insert(newEvent)
const [_id] = response.insertedIds
newEvent._id = _id
pubsub.publish("Event", { Event: { mutation: "CREATED", node: newEvent } })
return newEvent
},
},
Subscription: {
Event: {
subscribe: () => pubsub.asyncIterator("Event"),
},
},
Event: {
id: event => event._id.toString(),
owner: async (event, _, { mongo: { Users } }) => Users.findOne({ _id: event.ownerId }),
bookings: async (event, _, { mongo: { EventTickets } }) =>
EventTickets.find({ eventId: event._id }).count(),
tickets: async (event, _, { mongo: { EventTickets } }) =>
EventTickets.find({ eventId: event._id }).toArray(),
},
}
Any idea about how to deal with this (and/or how to get a proper error message to debug ^^).
Ok after searching, I found the solution/hack here.
SubscriptionServer.create(
{
execute, subscribe, schema,
onOperation: (message, params, webSocket) => {
return { ...params, context: {mongo} }
},
},
{ server, path: '/subscriptions' },
);
Thanks nharraud.

How to pass the values of a connection to a subscription?

I'm trying to return a subscription which consists of the following:
const postsSubscription = gql`
subscription postAdded {
postAdded {
id
title
description
author{
name
}
}
}
`
What happens is that Author is type of User, and I just pass an authorId. That means that I don't have the author name when I create the Post:
createPost: async (root, req, { posts }) => {
const Item = {
id: uuid.v4(),
authorId: '565dbdc0-36f2-4bba-be67-c126d0c71fff',
...req
}
await posts.create({ Item })
pubsub.publish('postAdded', { postAdded: Item })
return Item
},
Here is the Author resolver:
Post: {
author: async({ authorId }, req, { users }) => {
const Key = { id: authorId }
const { Item } = await users.get({ Key })
return Item
}
}
Here is the schema:
type Post {
id: ID
title: String
description: String
author: User #relation(name: "PostAuthor")
}
type User {
id: ID
name: String
email: String
password: String
posts: [Post] #relation(name: "UserPosts")
}
type PostPayload {
post: Post
}
type CreateUserPayload {
user: User
}
type Query {
allPosts: [Post]
allUsers: [User]
post(id: ID!): Post
user(id: ID!): User
}
type Mutation {
createPost(input: CreatePostInput!): PostPayload
updatePost(input : UpdatePostInput!): PostPayload
createUser(input : CreateUserInput!): CreateUserPayload
}
type Subscription {
postAdded: Post
}
input CreatePostInput {
title: String!
description: String!
}
input UpdatePostInput {
id: ID!
title: String!,
description: String!
}
input CreateUserInput {
name: String!
email: String!
password: String!
}
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
So, my question is, how to pass all required fields (including the connections) to the subscription?
I made it work, but not as I wanted.
1) I had to remove this part of the code:
Post: {
author: async({ authorId }, req, { users }) => {
const Key = { id: authorId }
const { Item } = await users.get({ Key })
return Item
}
}
and added this function to the createPost function itself:
createPost: async (root, { input }, { posts, users }) => {
const Key = { id: '3b1884b8-9ee7-4d9d-ab2f-ff32bcd69b9a' }
const user = await users.get({ Key })
const Item = {
id: uuid.v4(),
author: user.Item,
...input
}
await posts.create({ Item })
await pubsub.publish(POST_ADDED_TOPIC, { [POST_ADDED_TOPIC]: Item })
return { post: Item }
}
So this is kinda fixed. But, if you know how to fix this, using the first approach (Post: author thingy) I'll appreciate.

how to set many-to-many relation in graphql mutation?

I may be missing something, but can not find any information on Apollo docs about the way to set a many-to-many relation when creating a new entry.
When the relation is one-to-many it is as simple as setting the ID of the one-side of the relationship in the many-side object.
But let's pretend I am working with Books and Authors, how would I write a graphql query that creates a Book for one (or many?) Authors?
This should probably happen at the API layer on the GraphQL server (i.e. schema). For many-to-many relationships, you should have a "join" type to denote the BookAuthor many-to-many relationship, and then add an entry to that join type.
Essentially then you'll have a type called Book, another called Author, and finally one more called BookAuthor. And you can add a few mutations to be able to manage that relationship. Perhaps...
addToBookAuthorConnection
updateBookAuthorConnection
removeFromBookAuthorConnection
This is a conventional setup using a Relay-spec compliant API. You can read more about how to structure your API for many-to-many relationships here.
Then, you only need to call the addToBookAuthorConnection mutation from Apollo instead to be able to add to that many-to-many connection on your frontend.
Hope this helps!
If u r using apollo graph server with one to many relations then connectors.js, resolvers.js and schema.js files as given formats
schema.js
const typeDefinitions = `
type Author {
authorId: Int
firstName: String
lastName: String
posts: [Post]
}
type Post {
postId: Int
title: String
text: String
views: Int
author: Author
}
input postInput{
title: String
text: String
views: Int
}
type Query {
author(firstName: String, lastName: String): [Author]
posts(postId: Int, title: String, text: String, views: Int): [Post]
}
type Mutation {
createAuthor(firstName: String, lastName: String, posts:[postInput]): Author
updateAuthor(authorId: Int, firstName: String, lastName: String, posts:[postInput]): String
}
schema {
query: Query
mutation:Mutation
}
`;
export default [typeDefinitions];
resolvers.js
import { Author } from './connectors';
import { Post } from './connectors';
const resolvers = {
Query: {
author(_, args) {
return Author.findAll({ where: args });
},
posts(_, args) {
return Post.findAll({ where: args });
}
},
Mutation: {
createAuthor(_, args) {
console.log(args)
return Author.create(args, {
include: [{
model: Post,
}]
});
},
updateAuthor(_, args) {
var updateProfile = { title: "name here" };
console.log(args.authorId)
var filter = {
where: {
authorId: args.authorId
},
include: [
{ model: Post }
]
};
Author.findOne(filter).then(function (product) {
Author.update(args, { where: { authorId: args.authorId } }).then(function (result) {
product.posts[0].updateAttributes(args.posts[0]).then(function (result) {
//return result;
})
});
})
return "updated";
},
},
Author: {
posts(author) {
return author.getPosts();
},
},
Post: {
author(post) {
return post.getAuthor();
},
},
};
export default resolvers;
connectors.js
import rp from 'request-promise';
var Sequelize = require('sequelize');
var db = new Sequelize('test', 'postgres', 'postgres', {
host: '192.168.1.168',
dialect: 'postgres',
pool: {
max: 5,
min: 0,
idle: 10000
}
});
const AuthorModel = db.define('author', {
authorId: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true, field: "author_id" },
firstName: { type: Sequelize.STRING, field: "first_name" },
lastName: { type: Sequelize.STRING, field: "last_name" },
},{
freezeTableName: false,
timestamps: false,
underscored: false,
tableName: "author"
});
const PostModel = db.define('post', {
postId: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true, field: "post_id" },
text: { type: Sequelize.STRING },
title: { type: Sequelize.STRING },
views: { type: Sequelize.INTEGER },
},{
freezeTableName: false,
timestamps: false,
underscored: false,
tableName: "post"
});
AuthorModel.hasMany(PostModel, {
foreignKey: 'author_id'
});
PostModel.belongsTo(AuthorModel, {
foreignKey: 'author_id'
});
const Author = db.models.author;
const Post = db.models.post;
export { Author, Post };

Resources