FakeData "Error: Cannot return null for non-nullable field" - graphql

I made my first apollo server to try to understand it and so far I'm testing it with an array I created and I test my mutation with graphQL playground.
My data is a big array Like this:
const lists = [
{
id: 'list-0',
name: 'Example 1',
tasks: [{name:"task1", id:"1", completed: false},{name:"task2", id:"2", completed: true}, ]
},
{
id: 'list-1',
name: 'Example 2',
tasks: [{name:"task1", id:"1", completed: false},{name:"task2", id:"2", completed: true}, ]
},
];
So far I'm able to add a list name, delete a list and get a list by id. What I would like to do is, add a new task object to a list.
My mutation and type are like this:
type Task {
id: String!
name: String!
completed: Boolean!
}
type List {
id: String!
name: String!
tasks: [Task!]!
}
addTask(listId: String!, name: String!): Task!
The function addTask I made for my resolvers is this:
addTask(parents, {listId, name}) {
const newTask = lists.map((list) => {
if (listId === list.id) {
return {
...list,
tasks: [...list.tasks, { name, completed: false, id:"eaz"}],
};
}
console.log("test1", list)
return list
})
console.log("test2", newTask)
return newTask
},
When I use the graphQL playground I target one of my list.id like this one below but the console log tell me tasks: [ [Object], [Object] ] and the graphQL playground bring tell me tasks is null.
mutation Mutation {
addTask(listId: "task-0", name: "eze") {
name
}
}
Am I missing something about graphQL?Should I seperate the array tasks and lists?
Thanks for reading.

In your console logs, newTask looks like this:
[ [Object], [Object] ]
It's an array, not an object, and [].name is undefined, which gets converted to null.
Based on your type definitions, you must return an object that has this structure:
type Task {
id: String!
name: String!
completed: Boolean!
}
If instead, your code did something like this, I think it'll give you want you're saying it's returning:
addTask(parents, { listId, name }) {
let newTask
lists.forEach((list) => {
if (listId === list.id) {
newTask = { name, completed: false, id: 'eaz' }
list.tasks.push(newTask)
}
console.log('test1', list)
return list
})
console.log('test2', newTask)
return newTask
}
newTask here is the object { name, completed: false, id: 'eaz' }
Extra credit:
Alternative to .map or .forEach, which loops through EVERY ITEM NO MATTER WHAT, you can do something a bit more efficient like
addTask(parents, { listId, name }) {
const newTask = { name, completed: false, id: 'eaz' }
const list = lists.find((list) => {
return listId === list.id
}
if (!list) throw new Error('listId not found')
list.tasks.push(newTask)
console.log('test1', list)
console.log('test2', newTask)
return newTask
}

Related

Cannot Get Apollo addItem Mutation to work on the client keep getting 400 error

All I want to do is add an item to the items array in my Cart object.
What I am trying to do is simply execute my backend addItem mutation. After that I want to manually update the cache, but for now I am just re-fetching the query because I am unable to even successfully get the query to run.
In this code I am using the pothos withinput plugin: link to docs
I have tried:
Just putting the hardcoded input object into the addItem hook
Listing each Variable out one by one into the addItem hook
Describing the types of each prop in the original gql MUTATION
And passing the hardcoded input into the addItem hook via variables object
Passing hardcoded values into the actual addItem mutation
I have tried inputting the proper typing via a gql tag example below:
const THE_TYPE = gql`input addItemInput {
cartId: String!
id: String!
name: String!
price: Float!
}
`
const MUTATION = gql`
mutation AddItem($input: ${THE_TYPE}!) {
addItem(input: $input){carts{
id
items{
name
}}}
`;
*When I run the following mutation in my graphiql interface it works:
mutation MyMutation{
addItem(input:{
cartId: "2",
id: "12",
name: "New Item!",
price: 1900,
}){
items{
name
}
}}
However when I run the mutation below I get a 400 error:
Error: Response not successful: Received status code 400
import { useQuery, gql, useMutation } from '#apollo/client';
export default function DisplayCarts() {
interface Cart {
id: string;
items: string[];
}
interface Items {
}
const GET_CARTS = gql`
query {
carts{
id
items{
name
}}} `;
const MUTATION = gql`
mutation AddItem($input: Any) {
addItem(input: $input){
carts{
id
items{
name
}}
}}`;
const { loading, error, data } = useQuery(GET_CARTS)
const [addItem] = useMutation(MUTATION, {
refetchQueries: [{ query: GET_CARTS }]
// update(cache, { data: { addItem } }) {
// addItem is the response of the query of add item function
// console.log(data);
// #ts-ignore
// const { carts } = cache.readQuery({ query: GET_CARTS });
// cache.writeQuery({
// query: GET_CARTS,
// data: { carts: [...carts, addItem] }
// })
// }
})
function AddTodo() {
let theInput = {
cartId: "2",
id: "12",
name: "New Item!",
price: 1900,
quantity: 2
}
// #ts-ignore
addItem({ variables: { input: theInput } });
};
Here is my backend resolver function using pothos
Keep in mind my query does work in my graphiql interface so the issue is probably not on the backend
builder.mutationType({
fields: (t) => ({
addItem: t.fieldWithInput({
input: {
cartId: t.input.string({ required: true }),
id: t.input.string({ required: true }),
name: t.input.string({ required: true }),
price: t.input.int({ required: true }),
quantity: t.input.int({ required: true, defaultValue: 1 }),
},
type: Cart,
resolve: (_, { input: { cartId, ...input } }) => {
const cart = CARTS.find((cart) => cart.id === cartId);
if (!cart) {
throw new Error(`Cart with id ${cartId} not found`)
}
return {
id: cartId,
items: [...cart?.items, input]
}
}
}),
}),
})
The problem lies with:
mutation AddItem($input: Any) {
addItem(input: $input){…}
There is no Any in GraphQL. The 400 is a result of an invalid query/mutation. Note that you're not actually running the same mutation that you are in GraphiQL.
Try using an input type for example in your typeDefs (on the server), add:
input addItemInput {
cartId: String!
id: String!
name: String!
price: Float!
}
Then in your client code:
const MUTATION = gql`
mutation AddItem($input: addItemInput) {
addItem(input: $input){…}
}
`
Firstly some necessary information:
When using pothos with input plugin it formulates the query type for you following the following rule: ${ParentType.name}${Field.name}Input. I hoghly recomend you follow the link and look at the docs yourself so you can understand exactly how your query should look.
Here is the link to the corresponding docs
The correct query:
const MUTATION = gql`
mutation AddItem($input:MutationAddItemInput!) {
addItem(input: $input){
items{
name
}
}
}
`;
If you get a 400 error it is probably your query is just wrong
If you get a weird error with in it check your brackets you might be missing one or two

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

How to do a nested mutation resolver with nexus-prisma

I have the following datamodel:
type Job {
// ...
example: String
selections: [Selection!]
// ...
}
type Selection {
...
question: String
...
}
I define my object type so:
export const Job = prismaObjectType({
name: 'Job',
definition(t) {
t.prismaFields([
// ...
'example',
{
name: 'selections',
},
// ...
])
},
})
I do my resolver this way:
t.field('createJob', {
type: 'Job',
args: {
// ...
example: stringArg(),
selections: stringArg(),
// ...
},
resolve: (parent, {
example,
selections
}, ctx) => {
// The resolver where I do a ctx.prisma.createJob and connect/create with example
},
})
So now in the resolver I can receive the selections as json string and then parse it and connect/create with the job.
The mutation would look like this:
mutation {
createJob(
example: "bla"
selections: "ESCAPED JSON HERE"
){
id
}
}
I was wondering if there's anything more elegant where I could do something like:
mutation {
createJob(
example: "bla"
selections: {
question: "bla"
}
){
id
}
}
or
mutation {
createJob(
example: "bla"
selections(data: {
// ...
})
){
id
}
}
I've noticed that with nexus-prisma you can do stringArg({list: true}) but you can't really do objects.
My main question is what is the most elegant way to do either nested mutation or connect all in one.
You can use an inputObjectType as shown in the docs:
export const SomeFieldInput = inputObjectType({
name: "SomeFieldInput",
definition(t) {
t.string("name", { required: true });
t.int("priority");
},
});
Make sure to include the type as part of the types you pass to makeSchema. You can then use it to define an argument, like
args: {
input: arg({
type: "SomeFieldInput", // name should match the name you provided
}),
}
Now, the argument value will be available to your resolver as a regular JavaScript object, not a String. If you need a list of input objects, or want to make the argument required, you do so using the same options you would provide with when using a scalar -- list, nullable, description, etc.
Here's a complete example:
const Query = queryType({
definition(t) {
t.field('someField', {
type: 'String',
nullable: true,
args: {
input: arg({
type: "SomeFieldInput", // name should match the name you provided
}),
},
resolve: (parent, { input }) => {
return `You entered: ${input && input.name}`
},
})
},
})
const SomeFieldInput = inputObjectType({
name: "SomeFieldInput",
definition(t) {
t.string("name", { required: true });
},
});
const schema = makeSchema({
types: {Query, SomeFieldInput},
outputs: {
...
},
});
Then query it like:
query {
someField(
input: {
name: "Foo"
}
)
}
Or using variables:
query($input: SomeFieldInput) {
someField(input: $input)
}

Prisma graphql updateNode mutation

I'm trying to setup the updateNode mutation in graphql with Prisma running on GraphQL-yoga server. Here's the error I'm receiving when I try to run the mutation:
"Variable \"$_v0_data\" got invalid value { data: { name: \"Test\" }, where: { id: \"cjqulnr0yftuh0a71sdkek697\" } }; Field \"data\" is not defined by type CocktailUpdateInput.\nVariable \"$_v0_data\" got invalid value { data: { name: \"Test\" }, where: { id: \"cjqulnr0yftuh0a71sdkek697\" } }; Field \"where\" is not defined by type CocktailUpdateInput."
Here's my mutation resolver:
const Mutation = {
async updateCocktail(parent, args, ctx, info) {
const data = { ...args };
delete data.id;
const where = {id: args.id};
return await ctx.db.mutation.updateCocktail({ data, where }, info);
},
}
datamodel.prisma:
type Cocktail {
id: ID! #unique
name: String!
info: String
glass: Glass
ingredients: [Ingredient]
steps: [Step]
}
schema.graphql
type Mutation {
updateCocktail(data: CocktailUpdateInput!, where: CocktailWhereUniqueInput!): Cocktail
}
and finally here's what I'm trying to execute in playground:
mutation{
updateCocktail(
where: {id: "cjqulnr0y0tuh0a71sdkek697"},
data: {
name: "Test"
}
){
id
name
}
}
If I read your resolver correctly, you resolvers does the following:
Take the args and put them in data (without the id)
Take the id in the args and put it in where
But, in the playground, you give the following args:
args = {
where: {id: "cjqulnr0y0tuh0a71sdkek697"},
data: {
name: "Test"
}
}
They are already well formed! Which means your resolvers is gonna do the step as follow and build the following variables:
data = {
where: {id: "cjqulnr0y0tuh0a71sdkek697"},
data: {
name: "Test"
}
}
where = { id: null }
You can fix this two ways:
1/ Don't rebuild data and where in the resolvers and just pass the args down to prisma
2/ When calling your mutations, give it the args as follow:
updateCocktail(id: "abc", name: "Test") {...}
According to your error, the problem should lie in your playground execution. It is taking your "where" and "data" as data types.
You could try doing something more like this:
mutation UpdateCocktailMutation($data: CocktailUpdateInput!, $where: CocktailWhereUniqueInput!) {
updateCocktail(data: $data, where: $where) {
id
name
}
}
and in your bottom of the playground they have a query variable field.
Fill it will your variable data. Do correct my case sensitivity and naming conventions as I may have missed out on parts of it.

How to pass params to child property in GraphQL

i am pretty new to GraphQL, getting to become a huge fan :)
But, something is not clear to me. I am using Prisma with and GraphQL-Yoga with Prisma bindings.
I do not know how to pass params from my graphQL server to sub properties. Don't know if this is clear, but i will show it with code, thats hopefully easier :)
These are my types
type User {
id: ID! #unique
name: String!
posts: [Post!]!
}
type Post {
id: ID! #unique
title: String!
content: String!
published: Boolean! #default(value: "false")
author: User!
}
My schema.graphql
type Query {
hello: String
posts(searchString: String): [Post]
users(searchString: String, searchPostsTitle: String): [User]
me(id: ID): User
}
and my users resolver:
import { Context } from "../../utils";
export const user = {
hello: () => "world",
users: (parent, args, ctx: Context, info) => {
return ctx.db.query.users(
{
where: {
OR: [
{
name_contains: args.searchString
},
{
posts_some: { title_contains: args.searchPostsTitle }
}
]
}
},
info
);
},
me: (parent, args, ctx: Context, info) => {
console.log("parent", parent);
console.log("args", args);
console.log("info", info);
console.log("end_________________");
return ctx.db.query.user({ where: { id: args.id } }, info);
}
};
and my posts resolver
import { Context } from "../../utils";
export const post = {
posts: (parent, args, ctx: Context, info) => {
return ctx.db.query.posts(
{
where: {
OR: [
{
title_contains: args.searchString
},
{
content_contains: args.searchString
}
]
}
},
info
);
}
};
so, now :)
I am able to do the following when i am in the GraphQL playground on my prisma service:
{
user(where: {id: "cjhrx5kaplbu50b751a3at99d"}) {
id
name
posts(first: 1, after: "cjhweuosv5nsq0b75yc18wb2v") {
id
title
content
}
}
}
but i cant do it on the server, if i do something like that.. i am getting the error:
"error": "Response not successful: Received status code 400"
this is what i am trying:
{
me(id: "cjhrx5kaplbu50b751a3at99d") {
id
name
posts(first:1) {
id
title
content
}
}
}
does somebody know how i could do that?
since i have a custom type of user, posts does not have params like the generated one. Either i am using the the generated one, or modifying it to look like this:
type User {
id: ID!
name: String!
posts(where: PostWhereInput, orderBy: PostOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [Post!]
}
EDIT 2018 June 4th
# import Post from './generated/prisma.graphql'
type Query {
hello: String
posts(searchString: String): [Post]
users(searchString: String, where: UserWhereInput, orderBy: UserOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [User]
me(id: ID): User
}
type Mutation {
createUser(name: String!): User
createPost(
title: String!
content: String!
published: Boolean!
userId: ID!
): Post
}
I copied the params over from prisma.graphql manually.

Resources