Maybe someone who has managed to pass this step is willing to provide some indications.
I have a schema, a resolver, i request the query and i have a null response.
Please can you help on topic?
module.exports = {
Query: {
allLinks: async (root, {filter}, {mongo: {Links, Users}}) => {
let query = filter ? {$or: buildFilters(filter)} : {};
return await Links.find(query).toArray();
}
and the query request looks like this:
query LinkListPageQuery {
allLinks {
...LinkList_allLinks
}
}
fragment LinkList_allLinks on LinkConnection {
edges {
cursor
...Link_link
}
}
fragment Link_link on LinkEdge {
node {
id
description
url
}
}
My schema looks like this:
const typeDefs = `
type Link implements Node {
id: ID!
url: String!
description: String!
postedBy: User
votes: [Vote!]!
}
interface Node {
id: ID!
}
type Query {
allLinks(filter: LinkFilter, first: Int): [LinkConnection]
node(
id: ID!
): Node
}
type LinkEdge {
node: Link!
cursor: String
}
type LinkConnection {
pageInfo: PageInfo
edges: LinkEdge
count: Int
}
input LinkFilter {
OR: [LinkFilter!]
description_contains: String
url_contains: String
}
}
`;
PS: This language schema is done according to graphql-tools package.
Resolver:
Query: {
users: async (root, { first, after }, { mongo: { Users }, user }) => {
const queryData = await Users.find(query).toArray();
first = first || queryData.length;
after = after ? parseInt(fromCursor(after), 10) : 0;
const edges = queryData.map((node, i) => ({
cursor: toCursor(i+1),
node: node._id,
})).slice(after, first + after);
const slicedUser = edges.map(({ node }) => node);
return {
edges,
pageInfo: {
startCursor: edges.length > 0 ? edges[0].cursor : null,
hasNextPage: first + after < queryData.length,
endCursor: edges.length > 0 ? edges[edges.length - 1].cursor : null
},
count: queryData.length,
};
},
UserConnection: {
edges: ({ edges }) => edges,
pageInfo: ({ pageInfo }) => pageInfo,
count: ({ count }) => count,
},
UserEdge: {
node: async ({ node },data, {dataloaders: {userLoader}}) => {
const user = await userLoader.load(node);
return user;
},
cursor: ({ cursor }) => cursor,
},
Related
Problem:When I try and send/store data in my database I get this error. Specifically, I am trying to create/save a classroom with student names.
Tech Used:
Prisma/Postgres connected to AWS RDS and Next.js, deployed on Vercel, etc.
Error Message
PrismaClientValidationError: Argument data.classrooms.upsert.0.create.students.connectOrCreate.0.create.school.connect of type schoolWhereUniqueInput needs at least one argument.
Argument data.classrooms.upsert.0.update.students.upsert.0.create.school.connect of type schoolWhereUniqueInput needs at least one argument.
at Document.validate (/var/task/node_modules/#prisma/client/runtime/index.js:29501:20)
at serializationFn (/var/task/node_modules/#prisma/client/runtime/index.js:33060:19)
at runInChildSpan (/var/task/node_modules/#prisma/client/runtime/index.js:22550:12)
at PrismaClient._executeRequest (/var/task/node_modules/#prisma/client/runtime/index.js:33067:31)
at async PrismaClient._request (/var/task/node_modules/#prisma/client/runtime/index.js:32994:16)
at async profile (/var/task/.next/server/pages/api/user/profile.js:175:27)
at async Object.apiResolver (/var/task/node_modules/next/dist/server/api-utils/node.js:366:9)
at async NextNodeServer.runApi (/var/task/node_modules/next/dist/server/next-server.js:481:9)
at async Object.fn (/var/task/node_modules/next/dist/server/next-server.js:735:37)
at async Router.execute (/var/task/node_modules/next/dist/server/router.js:247:36) {
clientVersion: '4.9.0'
}
DB Models with relationships: school (1 to many w/students); students (many to many with classrooms); teachers (one to many with students, many to many with classrooms)
Code/Prisma Query
export default async (req, res) => {
...
classroom.students.forEach((student) => {
const totalStudentPoints = student.rewardsRecieved.reduce(
(totalPoints, reward) => {
return totalPoints + reward.pointValue;
},
0
);
groups[student.group.name] += totalStudentPoints;
});
return { ...classroom, groupsTotalPoints: groups };
});
user.classrooms = newClassrooms;
res.json(user);
} else {
console.log("Could Not Find User");
res.status(401).json({
error: "Not authorized",
});
}
}
if (req.method === "PUT") {
const connectStudents = (shouldUpsert) => {
const students = req.body.students;
return students.map((student) => {
const UNSAFEHASH = md5(student.id);
const studentQuery: any = {
where: {
id: student.id,
},
create: {
id: student.id,
firstName: student.firstName,
lastName: student.lastName,
profilePicture: student.profilePicture,
userKey: UNSAFEHASH,
school: {
connect: {
id: req.body.schoolId,
},
},
group: {
connect: {
id: student.group.id,
},
},
},
};
if (shouldUpsert) {
studentQuery.update = {
firstName: student.firstName,
lastName: student.lastName,
profilePicture: student.profilePicture,
userKey: UNSAFEHASH,
group: {
connect: {
id: student.group.id,
},
},
};
}
return studentQuery;
});
};
try {
const user = await prisma.staff.update({
where: {
id: session.id,
},
data: {
firstName: req.body.firstName,
lastName: req.body.lastName,
classrooms: {
upsert: [
{
where: {
id: req.body.classId || "-1",
},
create: {
// id: req.body.classId,
name: req.body.className,
subject: req.body.classSubject,
students: {
connectOrCreate: connectStudents(false),
},
},
update: {
name: req.body.className,
subject: req.body.classSubject,
students: {
upsert: connectStudents(true),
},
},
},
],
},
},
});
Take a look at the PUT request and the prima.staff.update method more specifically. I was looking at the UPSERT I have there, but I can't figure out what's wrong.
I have the following resolver for GraphQL:
const Post = require("../../models/Post");
module.exports = {
getAllActivePosts: async (userId) => {
try {
const posts = await Post.find({
userId: userId
})
.select(["name", "createdAt"])
.populate("posts", ["name", "createdAt"]);
return posts;
} catch (err) {
console.log(err);
throw err;
}
},
};
which tries to get all active posts by the ID of the user from the Post model:
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const PostSchema = new mongoose.Schema({
userId: {
type: Schema.Types.ObjectId,
ref: "User",
required: true,
},
content: {
type: String,
required: true,
},
createdAt: {
type: Date,
required: true,
}
});
module.exports = Post = mongoose.model("Post", PostSchema);
Here's the GraphQL Schema:
const { buildSchema } = require('graphql');
module.exports = buildSchema(`
type User {
_id: MongoId!
email: String!
password: String
}
type Post {
_id: MongoId!
userId: MongoId!
content: String!
createdAt: String!
}
scalar MongoId
input LoginInput {
email: String!
password: String!
}
type RootQuery {
login(email: String!, password: String!): AuthData!
getAllActivePosts(userId: MongoId!): [Post]
}
type RootMutation {
createUser(loginInput: LoginInput): AuthData!
}
schema {
query: RootQuery
mutation: RootMutation
}
`);
... and the GraphQL query I'm running in GraphiQL:
{
getAllActivePosts(userId: "5fbfc92312b90071179a160f") {
name
createdAt
}
}
For this, the result of the query is:
{
"errors": [
{
"message": "Cast to ObjectId failed for value \"{ userId: '5fbfc92312b90071179a160f' }\" at path \"userId\" for model \"Post\"",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"getAllActivePosts"
]
}
],
"data": {
"getAllActivePosts": null
}
}
Searched here for similar issues, tried wrapping userId in ObjectId, but nothing helped. What am I missing here?
I was go through this problem once a year ago with no solution till i get main concept of graphql.
Here you are passing string
{
getAllActivePosts(userId: "5fbfc92312b90071179a160f") {
name
createdAt
}
}
and graphql expecting to have mongoose.Types.ObjectId
getAllActivePosts(userId: MongoId!): [Post]
You need to do sync like
getAllActivePosts(userId: mongoose.Types.ObjectId("5fbfc92312b90071179a160f")) {
But using above way you are not eligible for run query in graphiQL becuse there is no mongoose defined.
type RootQuery {
login(email: String!, password: String!): AuthData!
getAllActivePosts(userId: String!): [Post]
}
Better solution is use userId input as string and then validate on your resolver function like
getAllActivePosts: async ({ userId }) => {
try {
if(mongoose.Types.ObjectId.isValid(userId)) {
const posts = await Post.find({
userId: userId
})
.select(["name", "createdAt"])
.populate("posts", ["name", "createdAt"]);
// you can;t return null you need to return array
return posts ? posts : []
} else {
// if mongoose id is wrong
return []
}
} catch(error) {
// it is better to throw error return blank array to complete flow
throw error
}
}
Turned out, I was using userId directly, whereas I should've used args.userId. The proper resolver below:
module.exports = {
getAllActivePosts: async (args) => {
try {
const posts = await Post.find({
userId: args.userId
})
.select(["name", "createdAt"])
.populate("posts", ["name", "createdAt"]);
return posts;
} catch (err) {
console.log(err);
throw err;
}
},
};
and for the schema:
getAllActivePosts(userId: String!): [Post]
I have a datamodel type for example
type XYZ {
id: ID!
relatedTable: ABC
someValue: String
}
Graphql part:
const createXYZ = gql`
mutation createXYZ(
relatedTableId: ID
someValue: String
) {
createXYZ(
data: {
relatedTableId: $relatedTableId
someValue: $someValue
}
) { ... }
`
const updateXYZ = gql`
mutation updateXYZ(
id: ID!
relatedTableId: ID
someValue: String
) {
updateXYZ(
data: {
relatedTableId: $relatedTableId
someValue: $someValue
},
where: { id: $id }
) { ... }
`
From the forntend I create XYZ record, with a mutation more or less like this:
createXYZ(variables: {
relatedTableId: 1,
someValue: 'cde'
})
server side:
createXYZ: async (obj, args, context, info) => {
return prisma.createXYZ({
data: {
relatedTable: { connect: { id: args.data.relatedTableId } },
someValue: args.data.someValue
}
})
}
Frontend side update looks similar to create
updateXYZ(variables: {
id: 2,
relatedTableId: 1,
someValue: 'cde'
})
On the server side, in order to prevent from updating fields with the same value I need to fetch first XYZ record and compare before updating:
updateXYZ: async (obj, args, context, info) => {
const currentXYZ = await prisma.XYZ({ where: args.where })
return prisma.updateXYZ({
data: {
relatedTable: currentXYZ.relatedTable.id !== args.data.relatedTableId
? { connect: { id: args.data.relatedTableId } }
: undefined,
someValue: currentXYZ.someValue !== args.data.someValue
? args.data.someValue
: undefined
},
where: args.where
})
}
Does Prisma have any automatic way of detecting whether the received field value is the same as the current field value or is fetching and comparing values the only solution to this issue?
Graphql return Oject with null id.
with mongodb.
It looks strange to me.
If I delete new GraphQLNonNull() on MailType id,
It works with id: null, another fields working fine.
const MailType = new GraphQLObjectType({
name: 'Mail',
fields: () => ({
id: { type: new GraphQLNonNull(GraphQLID), },
...
})
const Query = {
mails: {
type: new GraphQLList(MailType),
args: {
senderId: { type: GraphQLID },
isOffline: { type: GraphQLBoolean },
},
async resolve(root, args, req, ctx) {
if (args.isOffline === false) {
let a = await model.aggregate([
{ $match: { isOffline: false } },
]);
let b = await model.find({ isOffline: false });
console.log(JSON.stringify(a) == JSON.Stringify(b)) /// return true
return a // error
return b // working
}
return model.find({senderId: args.senderId});
}
}
}
// with a
"errors": [
{
"message": "Cannot return null for non-nullable field Mail.id."
}]
I am in trouble for 2 hours but I do not get the answer.
Can anybody help me?
You probably have a mistake in your mongodb schema, not in graphQl.
make sure you did not define you id by id key, it should be _id.
for example if you are using mongoose it can be something like this:
const MailSchema = new Schema({
_id: {
type: String,
unique: true,
},
....
....
});
Here is the code for schema
schema stores questions for a particular course and which contains chapter and there question
questions: [
{
Chapter: String,
chques: [
{
description: String,
questions: [
{
question: String,
options: [String],
answer: Number,
Explanation: String,
code: { type: String, default: null },
images: [{ type: String, default: null }],
level: String
}
]
}
]
}
],
Here is the code for route
Router.get(
"/queformock/:course_id",
passport.authenticate("jwt", { session: false }),
(req, res) => {
Courses.findOne({ _id: req.params.course_id })
.lean()
.exec()
.then(course => {
if (!course) res.status(404).json({ course: "course not found" });
else {
let coursepass = Object.assign({}, course);
console.log("coursepass1: ", coursepass); //before calling levelQuestion it output as expected
let level2 = levelQuestion(coursepass, "medium");
console.log("coursepass2: ", coursepass);
let level3 = levelQuestion(coursepass, "hard");
console.log("coursepass3: ", coursepass);
res.json(level2);
}
});
}
);
Here is the levelQuestion function code
function levelQuestion(coursed, type) {
let arr = [];
coursed.questions.forEach(item => {
item.chques.forEach(i => arr.unshift(i));
});
arr.forEach(item => {
_.remove(item.questions, i => {
return i.level !== type;
});
});
_.remove(arr, item => {
return item.questions == false;
});
return arr;
}
now the problem is on every function call of levelQuestion coursepass is changed...why??