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.
Related
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)
}
I am trying to get an optimistic response when I add a task to my todo list:
ADD_TASK and GET_TASK from query.ts
export const GET_TASKS = gql`
subscription {
queryTask {
id
title
completed
user {
username
}
}
}
`;
export const ADD_TASK = gql`
mutation addTask($task: AddTaskInput!) {
addTask(input: [$task]) {
task {
id
title
completed
}
}
}
`;
addTask() function
const newId = Math.round(Math.random() * -1000000);
await addTask({
variables: {
task: {
title: text,
completed: false,
user: { username: user?.email },
},
},
optimisticResponse: {
__typename: "Mutation",
addTask: {
__typename: "AddTaskPayload",
task: {
__typename: "Task",
id: newId,
title: text,
completed: false,
user: {
__typename: "User",
username: user?.email,
},
},
},
},
update(cache, { data: addTask }: any) {
const queryTask: any = cache.readQuery({
query: GET_TASKS,
});
cache.writeQuery({
query: GET_TASKS,
data: {
queryTask: [...queryTask.queryTask, addTask.addTask.task],
},
});
},
});
UPDATE
So, I got it working normally, now I just need to get it working with:
1.) Subscriptions
2.) The ID problem... it generates a random ID here instead of knowing what it should be (any suggestions)?
I am using my repository with Dgraph here.
(This does not include the optimistic version)
Any suggestions?
J
this is my InputType in schema.graphql:
input RegisterInput {
birthday: String!
email: String!
firstName: String!
gender: String!
interests: [String!]!
lastName: String!
password: String!
}
and this is my mutation:
const RegisterInput = inputObjectType({
name: 'RegisterInput',
definition(t) {
t.string('birthday', { nullable: false });
t.string('email', { nullable: false });
t.string('firstName', { nullable: false });
t.string('lastName', { nullable: false });
t.string('gender', { nullable: false });
t.string('password', { nullable: false });
t.list.field('interests', {
type: 'String',
nullable: false,
});
},
});
const Mutation = objectType({
name: 'Mutation',
definition(t) {
t.field('register', {
type: User,
args: {
data: arg({ type: RegisterInput }),
},
resolve: async (
_root,
{ data: { password, interests, ...userData } },
{ prisma }
) => {
const hashedPassword = await bcrypt.hash(password, 10);
const user = await prisma.user.create({
data: {
...userData,
interests: [...interests],
password: hashedPassword,
},
});
return user;
},
});
my interests is just an array of strings, .e.g: ['abcd', 'def']
but i got this error:
Unknown arg `0` in data.interests.0 for type UserCreateInterestInput. Available args:
type UserCreateInterestsInput {
set?: List<String>
}
that error will repeat depending of how many items is in the array, e.g.: Unknown arg '1' and so on, same error message, how do i fix this?
You must provide a list of strings to set argument, such as:
type UserCreateInterestsInput {
set?: List<String>
}
Refer to this issue for more information.
const Mutation = objectType({
name: 'Mutation',
definition(t) {
t.field('register', {
type: User,
args: {
data: arg({ type: RegisterInput }),
},
resolve: async (
_root,
{ data: { password, interests, ...userData } },
{ prisma }
) => {
const hashedPassword = await bcrypt.hash(password, 10);
const user = await prisma.user.create({
data: {
...userData,
interests: {set: interests},
password: hashedPassword,
},
});
return user;
},
});
Hope this helps
Happened to me earlier, turns out it was a query mistake.
mutation {
createFruit(data:{
name: "Banana",
images: {
set: ["image_1.img", "image_2.img"]
}
}) {
name
images
}
}
Note it's not images: ["image_1.img", "image_2.img"]
fyi with prisma you can do t.model.interest() when defining objectType
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']],
});
},
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.