Trying to delete using Mutation but its giving the error saying authorsCollection.delete is not a function - graphql

When I try to run it, It is giving me the error.Trying to delete using Mutation but its giving the error saying "authorsCollection.delete" is not a function
const Mutation = new GraphQLObjectType({
name: "Mutations",
fields: {
DeleteAuthor: {
type: Author,
args: {
_id: {type: new GraphQLNonNull(GraphQLString)},
name: {type: GraphQLString},
},
resolve: function(rootValue, args) {
let author = Object.assign({}, args);
console.log(args);
return authorsCollection.delete(author._id)
.then(_ => author);
}
}
What should be edited in the code so that I can implemented the Delete operation?
It is giving me the error as below
{
"data": {
"DeleteAuthor": null
},
"errors": [
{
"message": "authorsCollection.delete is not a function",
"locations": [
{
"line": 2,
"column": 3
}
]
}
]
}

The error message authorsCollection.delete is not a function indicates that authorsCollection does not have any function named delete. In your jsfiddle code, I see you have used promised-mongo library. The API to remove documents is remove, not delete.
Change to
authorsCollection.remove({_id: ObjectId(author._id)})
The _id passed is a string. But the _id property of author in DB is of type ObjectID. So, you have to convert it to ObjectID type. You do so by using promised-mongo's ObjectId. So, import it in the beginning:
import {ObjectId} from 'promised-mongo';

Related

How to handle graphql errors on Apollo server? [duplicate]

In an express-graphql app, I have a userLogin resolver like so:
const userLogin = async ({ id, password }), context, info) => {
if (!id) {
throw new Error('No id provided.')
}
if (!password) {
throw new Error('No password provided.')
}
// actual resolver logic here
// …
}
If the user doesn't provide an id AND a password, it will throw only one error.
{
"errors": [
{
"message": "No id provided.",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"userLogin"
]
}
],
"data": {
"userLogin": null
}
}
How is it possible to throw multiple errors in the errors response array?
There is no way to throw an array of errors in JavaScript or otherwise have a single resolver reject with more than one error. A GraphQL response includes an errors array and not just a single error object because the total response can include multiple errors when those errors originate from different fields. Consider this schema and resolvers:
type Query {
a: String
b: String
c: String
}
const resolvers = {
Query: {
a: () => { throw new Error('A rejected') },
b: () => { throw new Error('B rejected') },
c: () => 'Still works!',
},
}
If you query all three fields...
query {
a
b
c
}
Your data will look something like this:
{
"errors": [
{
"message": "A rejected",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"a"
]
},
{
"message": "B rejected",
"locations": [
{
"line": 3,
"column": 3
}
],
"path": [
"b"
]
}
],
"data": {
"a": null,
"b": null,
"c": "Still works!"
}
}
This is because GraphQL supports partial responses. However, keep in mind that this works because the fields are nullable. If they were non-null, those errors would bubble up to the closest nullable parent field.
Here are some alternative approaches:
You can utilize formatError to change how the errors returned by GraphQL are displayed to the client. That means you can include any sort of extra information with your errors, like an error code or multiple error messages. A simple example:
// The middleware
app.use('/graphql', graphqlExpress({
schema: schema,
formatError: (error) => ({
message: error.message,
path: error.path,
locations: error.locations,
errors: error.originalError.details
})
}))
// The error class
class CustomError extends Error {
constructor(detailsArray) {
this.message = String(details)
this.details = details
}
}
// The resolver
const userLogin = async ({ id, password }), context, info) => {
const errorDetails = []
if (!id) errorDetails.push('No id provided.')
if (!password) errorDetails.push('No password provided.')
if (errorDetails.length) throw new CustomError(errorDetails)
// actual resolver logic here
}
Your response then looks more like this:
{
"errors": [
{
"message": "[No id provided.,No password provided.]",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"userLogin"
]
"errors" [
"No id provided.",
"No password provided."
]
}
],
"data": {
"userLogin": null
}
}
That said, there's something a bit unsavory about returning user-facing error messages alongside GraphQL validation errors. Another approach that some APIs have taken is to include an errors field alongside the actual mutation response. For example:
type Mutation {
userLogin: UserLoginResponse
}
type UserLoginResponse {
response: User
errors: [String!]
}
You can also use unions to achieve a similar effect:
type Mutation {
userLogin: UserLoginResponse
}
type Errors {
errors: [String!]!
}
union UserLoginResponse = User | Errors

Partial Update Mutations(GraphQL)

How can I be able to update a node with only one field change and leave the rest of the fields alone?
My User Type
type User {
id: ID!
user_id: String!
username: String!
email: String!
role: Role!
isVerified: Boolean!
}
My Input Types
input UserUpdateInput {
user_id: String
username: String
email: String
password: String
role: Role
isVerified: Boolean
}
input UserWhereUniqueInput {
id: ID
user_id: String
email: String
}
My Mutation type
type Mutation {
updateUser(data: UserUpdateInput!, where: UserWhereUniqueInput): User
}
My Resolver
function updateUser(root, args, context, info){
return context.db.mutation.updateUser({
data: args.data,
where: {
id: args.where.id
}
}, info)
}
This is the request am sending on the GraphQL playground
mutation{
updateUser(
data: {
isVerified: true
}
where:{
user_id : "afc485b"
}
)
{
isVerified
}
}
This is the error am getting
{
"errors": [
{
"message": "Cannot read property 'mutation' of undefined",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"updateUser"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"stacktrace": [
"TypeError: Cannot read property 'mutation' of undefined"
Someone help me. What am I missing?
After updating my server as suggested by Daniel Rearden on the answer section, am getting a new error
{
"message": "Cannot read property 'updateUser' of undefined",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"updateUser"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"stacktrace": [
"TypeError: Cannot read property 'updateUser' of undefined"
The error is the result of not correctly adding the db property to you context. Assuming you're still using version 1, your code should look something like this:
const { prisma } = require('./generated/prisma-client')
const server = new ApolloServer({
...
context: {
db: prisma,
},
})
First thing I notice is you GQL query is not correct.
Yours:
mutation{
updateUser(
data: {
isVerified: true
}
where:{
user_id : "afc485b"
}
)
{
isVerified
}
}
after the word "mutation" you set a name to the call, i.e.
"UpdateUser" but can literally be anything. for each part of the
where clause you need to make the check value an object, i.e.
where: { myProperty: {eq: "some value"}}
So your query should be more like this:
mutation UpdateUser {
updateUser(
data: {isVerified: true}
where:{user_id : {eq: "afc485b"}}
)
{
isVerified
}
}
Hope that helps a little... I didn't fully read the rest but thought this would help with the initial error you were getting.

How to properly format data with AppSync and DynamoDB when Lambda is in between

Receiving data with AppSync directly from DynamoDB seems working for my case, but when I try to put a lambda function in between, I receive errors that says "Can't resolve value (/issueNewMasterCard/masterCards) : type mismatch error, expected type LIST"
Looking to the AppSync cloudwatch response mapping output, I get this:
"context": {
"arguments": {
"userId": "18e946df-d3de-49a8-98b3-8b6d74dfd652"
},
"result": {
"Item": {
"masterCards": {
"L": [
{
"M": {
"cardId": {
"S": "95d67f80-b486-11e8-ba85-c3623f6847af"
},
"cardImage": {
"S": "https://s3.eu-central-1.amazonaws.com/logo.png"
},
"cardWallet": {
"S": "0xFDB17d12057b6Fe8c8c434653456435634565"
},...............
here is how I configured my response mapping template:
$utils.toJson($context.result.Item)
I'm doing this mutation:
mutation IssueNewMasterCard {
issueNewMasterCard(userId:"18e946df-d3de-49a8-98b3-8b6d74dfd652"){
masterCards {
cardId
}
}
}
and this is my schema :
type User {
userId: ID!
masterCards: [MasterCard]
}
type MasterCard {
cardId: String
}
type Mutation {
issueNewMasterCard(userId: ID!): User
}
The Lambda function:
exports.handler = (event, context, callback) => {
const userId = event.arguments.userId;
const userParam = {
Key: {
"userId":{S:userId}
},
TableName:"FidelityCardsUsers"
}
dynamoDB.getItem(userParam, function(err, data) {
if (err) {
console.log('error from DynamDB: ',err)
callback(err);
} else {
console.log('mastercards: ',JSON.stringify(data));
callback(null,data)
}
})
I think the problem is that the getItem you use when you use the DynamoDB datasource is not the same as the the DynamoDB.getItem function in the aws-sdk.
Specifically it seems like the datasource version returns an already marshalled response (that is, instead of something: { L: [ list of things ] } it just returns something: [ list of things]).
This is important, because it means that $utils.toJson($context.result.Item) in your current setup is returning { masterCards: { L: [ ... which is why you are seeing the type error- masterCards in this case is an object with a key L, rather than an array/list.
To solve this in the resolver, you can use the $util.dynamodb.toDynamoDBJson(Object) macro (https://docs.aws.amazon.com/appsync/latest/devguide/resolver-util-reference.html#dynamodb-helpers-in-util-dynamodb). i.e. your resolver should be:
$util.dynamodb.toDynamoDBJson($context.result.Item)
Alternatively you might want to look at the AWS.DynamoDB.DocumentClient class (https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html). This includes versions of getItem, etc. that automatically marshal and unmarshall the proprietary DynamoDB typing back into native JSON. (Frankly I find this much nicer to work with and use it all the time).
In that case you can keep your old resolver, because you'll be returning an object where masterCards is just a JSON array.

Error while trying to run a GraphQL query recursively, along with queried results

This is closely related to my last question here. In short, I have 2 schemas, dbPosts and dbAuthors. They look somewhat like this (I've omitted some fields here for the sake of brevity):
dbPosts
id: mongoose.Schema.Types.ObjectId,
title: { type: String },
content: { type: String },
excerpt: { type: String },
slug: { type: String },
author: {
id: { type: String },
fname: { type: String },
lname: { type: String },
}
dbAuthors
id: mongoose.Schema.Types.ObjectId,
fname: { type: String },
lname: { type: String },
posts: [
id: { type: String },
title: { type: String }
]
I'm resolving my post queries like this:
const mongoose = require('mongoose');
const graphqlFields = require('graphql-fields');
const fawn = require('fawn');
const dbPost = require('../../../models/dbPost');
const dbUser = require('../../../models/dbUser');
fawn.init(mongoose);
module.exports = {
// Queries
Query: {
posts: (root, args, context) => {
return dbPost.find({});
},
post: (root, args, context) => {
return dbPost.findById(args.id);
},
},
Post: {
author: (parent, args, context, ast) => {
// Retrieve fields being queried
const queriedFields = Object.keys(graphqlFields(ast));
console.log('-------------------------------------------------------------');
console.log('from Post:author resolver');
console.log('queriedFields', queriedFields);
// Retrieve fields returned by parent, if any
const fieldsInParent = Object.keys(parent.author);
console.log('fieldsInParent', fieldsInParent);
// Check if queried fields already exist in parent
const available = queriedFields.every((field) => fieldsInParent.includes(field));
console.log('available', available);
if(parent.author && available) {
return parent.author;
} else {
return dbUser.findOne({'posts.id': parent.id});
}
},
},
};
And I'm resolving all author queries like this:
const mongoose = require('mongoose');
const graphqlFields = require('graphql-fields');
const dbUser = require('../../../models/dbUser');
const dbPost = require('../../../models/dbPost');
module.exports = {
// Queries
Query: {
authors: (parent, root, args, context) => {
return dbUser.find({});
},
author: (root, args, context) => {
return dbUser.findById(args.id);
},
},
Author: {
posts: (parent, args, context, ast) => {
// Retrieve fields being queried
const queriedFields = Object.keys(graphqlFields(ast));
console.log('-------------------------------------------------------------');
console.log('from Author:posts resolver');
console.log('queriedFields', queriedFields);
// Retrieve fields returned by parent, if any
const fieldsInParent = Object.keys(parent.posts[0]._doc);
console.log('fieldsInParent', fieldsInParent);
// Check if queried fields already exist in parent
const available = queriedFields.every((field) => fieldsInParent.includes(field));
console.log('available', available);
if(parent.posts && available) {
// If parent data is available and includes queried fields, no need to query db
return parent.posts;
} else {
// Otherwise, query db and retrieve data
return dbPost.find({'author.id': parent.id, 'published': true});
}
},
},
};
Again, I've left out bits not relevant to this question, such as mutations, in the interest of brevity. My objective is to make all queries work recursively while also optimizing database lookups. But somehow I'm unable to accomplish this. Here's one query I'm running, for instance:
{
posts{
id
title
author{
first_name
last_name
id
posts{
id
title
}
}
}
}
And it returns this:
{
"errors": [
{
"message": "Cannot return null for non-nullable field Post.author.",
"locations": [
{
"line": 5,
"column": 5
}
],
"path": [
"posts",
1,
"author"
]
}
],
"data": {
"posts": [
{
"id": "5ba1f3e7cc546723422e62a4",
"title": "A Title!",
"author": {
"first_name": "Bill",
"last_name": "Erby",
"id": "5ba130271c9d440000ac8fc4",
"posts": [
{
"id": "5ba1f3e7cc546723422e62a4",
"title": "A Title!"
}
]
}
},
null
]
}
}
If you notice, this query does return all values requested, but also adds an error message against the post.author query! What could be causing this?
I haven't included the entire codebase so as not to make things confusing, but should you wish to take a look, it's up on Github and a GraphiQL interface is up at https://graph.schandillia.com should you wish to see the results for yourself.
Thank you so much for your time, if you've come this far. Would really appreciate any pointer in the right direction!"
P.S.: If you notice, I'm logging the values of 3 variables in each resolver for debugging purposes:
queriedFields: An array of all fields being queried
fieldsInParent: An array of all fields being returned in the resolver's parent property
available: A boolean showing if all queriedFields members exist in fieldsInParent
And when I run a simple query like this:
{
posts{
id
author{
id
posts{
id
}
}
}
}
This is what gets logged:
-------------------------------------------------------------
from Post:author resolver
queriedFields [ 'id', 'posts' ]
fieldsInParent [ '$init', 'id', 'first_name', 'last_name' ]
available false
-------------------------------------------------------------
from Post:author resolver
queriedFields [ 'id', 'posts' ]
fieldsInParent [ '$init', 'id', 'first_name', 'last_name' ]
available false
-------------------------------------------------------------
from Author:posts resolver
queriedFields [ 'id' ]
fieldsInParent [ 'id', 'title' ]
available true
Shouldn't the post:author resolver execute only once? Also, it's funny how in the first 2 logs, fieldsInParent is missing the posts field even when the schema for author includes such a field.
Your query result does not in fact include all the requested data. The posts query resolves to an array that includes one Post object and a null. The null is there because GraphQL tried to fully resolve the other Post object and could not -- it encountered a validation error, namely that the post's author resolved to null.
You can change your schema to make the author field nullable, which would get rid of the error but would still leave you with the null post. Presumably, if a post exists, it should have an author (although with MongoDB I guess it's very possible you just have some bad data). If you look inside your resolver, there's two return statements -- one of them (probably the db call) is returning null for that second post.
As an aside, as a client, you probably don't want to deal with nulls inside the array and want an empty array instead of a null for the whole field. When using lists (arrays), you may want to make them both non-nullable and make each item in that list non-nullable as well. You do so like this:
posts: [Post!]!
You still need to ensure your resolver logic prevents those nulls from happening, but adding the validation can help you catch that sort of behavior more easily.

Handling errors in mutations

Let's say I'm trying to create a bike as a mutation
var createBike = (wheelSize) => {
if (!factoryHasEnoughMetal(wheelSize)) {
return supplierError('Not enough metal');
}
return factoryBuild(wheelSize);
}
What happens when there's not enough steel for them shiny wheels? We'll probably need an error for the client side. How do I get that to them from my graphQL server with the below mutation:
// Mutations
mutation: new graphql.GraphQLObjectType({
name: 'BikeMutation',
fields: () => ({
createBike: {
type: bikeType,
args: {
wheelSize: {
description: 'Wheel size',
type: new graphql.GraphQLNonNull(graphql.Int)
},
},
resolve: (_, args) => createBike(args.wheelSize)
}
})
})
Is it as simple as returning some error type which the server/I have defined?
Not exactly sure if this is what you after...
Just throw a new error, it should return something like
{
"data": {
"createBike": null
},
"errors": [
{
"message": "Not enough metal",
"originalError": {}
}
]
}
your client side should just handle the response
if (res.errors) {res.errors[0].message}
What I do is passing a object with errorCode and message, at this stage, the best way to do is stringify it.
throw new Errors(JSON.stringify({
code:409,
message:"Duplicate request......"
}))
NOTE: also you might be interested at this library https://github.com/kadirahq/graphql-errors
you can mask all errors (turn message to "Internal Error"), or define userError('message to the client'), which these will not get replaced.

Resources