Pass argument child type graphql - 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']],
});
},

Related

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

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)
}

GraphQL always return null

I created a resolver and graphql file about user creation.
The data type seems to fit, and I have asynchronous processing, but the result always return null. I don't know why.
Resolver (createAccount.js)
import { prisma, generateToken, changePhoneNumber } from "../../../utils";
import bcrypt from "bcryptjs";
export default {
Mutation: {
createAccount: async (_, args) => {
const {
username,
password,
password2,
email,
phoneNum,
bio,
avatarUrl,
} = args;
if (password !== password2) {
throw new Error("two password aren't same each other");
}
const encryptPw = await bcrypt.hash(password, 10);
const newPhoneNumber = await changePhoneNumber(phoneNum, "+82");
const user = await prisma.user.create({
data: {
username,
email,
password: encryptPw,
phoneNum: newPhoneNumber,
bio,
avatarUrl,
},
});
const token = generateToken(user.id);
return { token, user };
},
},
};
GraphQL file (createAccount.graphql)
type Mutation {
createAccount(
username: String!
email: String!
password: String!
password2: String!
phoneNum: String!
bio: String
avatarUrl: String
): AuthPayload!
}
type AuthPayload {
token: String
user: User
}
utils.js
import { PrismaClient } from "#prisma/client";
import jwt from "jsonwebtoken";
export const prisma = new PrismaClient();
export const changePhoneNumber = (phoneNum, locationNum) => {
var leftStr = locationNum;
var rightStr = phoneNum.slice(1, phoneNum.length);
var newStr = leftStr + rightStr;
return newStr;
};
export const generateToken = (id) => jwt.sign({ id }, process.env.JWT_SECRET);
models.graphql
type User {
id: ID!
avatarUrl: String
username: String!
email: String!
password: String!
phoneNum: String!
emailSecret: String
phoneSecret: String
bio: String
rooms: [Room]
createdAt: String
messages: [Message]
sender: [User]
receiver: [User]
}
I read the answers to other similar questions, but most said that I should fit the data type or asynchronous processing.
(Why does a GraphQL query return null?) But my code used code for asynchronous processing, and I guess I matched the data type. Why does this code always return null?
Also, in addition to this Mutation, all other Query, Mutation and Subscriptions return nulls
Based on the error, it looks like GraphQL doesn't think you've provided a resolver for the createAccount field at all. The issue is with how you're merging your resolvers. This is your code:
const allTypes = fileLoader(path.join(__dirname, "api/**/*.graphql"));
const allResolvers = fileLoader(path.join(__dirname, "api/**/*.js"));
const schema = makeExecutableSchema({
typeDefs: mergeTypes(allTypes),
resolvers: mergeResolvers(allResolvers),
});
This is the resulting value of allResolvers:
[ { Query: { TestQL: [Function: TestQL] } },
{ Mutation: { addCategory: [Function: addCategory] } },
{ Mutation: { deleteCategory: [Function: deleteCategory] } },
{ Mutation: { editCategory: [Function: editCategory] } },
{ Mutation: { newMessage: [Function: newMessage] } },
{ Subscription: { subMessage: [Object] } },
[Function: _default],
{ Mutation: { createRoom: [Function: createRoom] } },
[Function: _default],
{ Query: { getRooms: [Function: getRooms] } },
{ Mutation: { createAccount: [Function: createAccount] } },
{ Mutation: { deleteUser: [Function: deleteUser] } },
{ Mutation: { editProfile: [Function: editProfile] } },
{ Query: { findEmail: [Function: findEmail] } },
{ Mutation: { login: [Function: login] } },
{ Mutation: { requestEmailSecret: [Function: requestEmailSecret] } },
{ Mutation: { resetPassword: [Function: resetPassword] } },
{ Query: { searchUser: [Function: searchUser] } },
{ Query: { seeProfile: [Function: seeProfile] } } ]
Two of your modules export a function instead of an object:
export default () => {...}
As a result, what's returned by mergeResolvers ends up being a function, not an object. So you're not providing a resolver map at all to makeExecutableSchema. You need to fix the default exports for those two modules for the resolvers to be merged correctly.

Unknown argument in GraphiQL trying to pass an ID from the client to the GraphQL server?

Im using Apollo 2. With the GraphiQL interface I can run a query which returns all groups fine. However when I try to pass a name to find just 1 group it doenst work.
Schema
type Query {
hi: String
groups: [Group]
group(name: String): Group
}
type Group {
name: String
}
Resolvers:
Query: {
hi() {
return 'howdy';
},
groups() {
return Groups.find().fetch();
},
group(one, two, three) {
console.log('group resolver called');
console.log(one, two, three);
//return Groups.findOne(two.name);
},
},
This is my GraphiQL groups query which works fine:
{
groups {
name
}
}
But my group query returns an error:
{
group(name: "some name") {
name
}
}
{
"errors": [
{
"message": "Unknown argument \"name\" on field \"group\" of type \"Query\".",
"locations": [
{
"line": 2,
"column": 9
}
]
}
]
}
UPDATE - This is my full file:
import { createApolloServer } from 'meteor/apollo';
import { makeExecutableSchema } from 'graphql-tools';
import merge from 'lodash/merge';
import GroupsSchema from '../../api/groups/Groups.graphql';
import GroupsResolvers from '../../api/groups/resolvers';
const typeDefs = [GroupsSchema];
const resolvers = merge(GroupsResolvers);
const schema = makeExecutableSchema({
typeDefs,
resolvers,
});
createApolloServer({ schema });
In resolvers.js
import Groups from './groups';
export default {
Query: {
hi() {
return 'howdy';
},
groups() {
return [{ name: '1', test: 'test 1' }, { name: '2', test: 'test 2' }];
// return Groups.find().fetch();
},
group(one, two, three) {
console.log('group resolver called');
console.log(one, two, three);
// return Groups.findOne('Apon9HKE9MeZqeXsZ');
},
},
};
In Groups.graphql
type Query {
hi: String
groups: [Group]
group(name: String): Group
}
type Group {
name: String
}
I think your typeDefs and resolvers are wrongs, try:
typeDefs
const typeDefs = `
type Query {
hi: String
groups: [Group]
group(name: String): Group
}
type Group {
name: String
}`;
Resolvers
export default {
Query: {
hi() {
return 'howdy';
},
groups() {
return [{ name: '1', test: 'test 1' }, { name: '2', test: 'test 2' }];
// return Groups.find().fetch();
},
group(_, { name }) {
console.log('group resolver called');
console.log(one, two, three);
// return Groups.findOne('Apon9HKE9MeZqeXsZ');
},
},
};

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.

Resources