Nestjs Graphql the mutation/query input dto is empty - graphql

I don't know what I miss but the input for the mutation or the queries is always empty
when I debug seems that the input is empty object
This is my code:
resolver
#Query(() => String)
async testMutation(#Args('args') args: UpvotePostInput) {
return args.postId;
}
the dto
import { InputType, Field } from '#nestjs/graphql';
#InputType()
export class UpvotePostInput {
#Field(() => String)
postId: string;
}
the empty object
the error
{
"errors": [
{
"message": "Cannot return null for non-nullable field Query.testMutation.",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"testMutation"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"stacktrace": [
"Error: Cannot return null for non-nullable field Query.testMutation.",
" at completeValue (/....js:559:13)",
" at /.....xecute.js:469:16",
" at processTicksAndRejections (internal/process/task_queues.js:97:5)",
" at async Promise.all (index 0)"
]
}
}
}
],
"data": null
}

The solution is to add #IsNotEmpty() or #IsOptional() decorator to the property:
#InputType()
export class UpdateCarInput extends PartialType(CreateCarInput) {
#Field(() => Int)
#IsNotEmpty()
id: number;
}
weird, but this resolve the problem.

I investigated this strange behaviour of nestjs and find out that this happens when use use global validation pipe with whitelist property set to true.
I tested this global validation pipe
app.useGlobalPipes(
new ValidationPipe({
// whitelist: true,
transform: true,
transformOptions: { enableImplicitConversion: true },
}),
);
for this resolver method
#Mutation()
createPost(#Args('input') input: CreatePostInput) {
return this.postsService.create(input);
}
CreatePostInput class is generated with nestjs graphql schema first approach
//graphql.ts file
export class CreatePostInput {
title: string;
authorId: number;
}
nestjs-cli.json file is configured to use #nestjs/graphql plugin
"compilerOptions": {
"plugins": [
{
"name": "#nestjs/graphql",
"options": {
"introspectComments": true
}
}
]
}
The input object is not an empty object

Add deep partial
https://gist.github.com/navix/6c25c15e0a2d3cd0e5bce999e0086fc9
#Query(() => String)
async testMutation(#Args('args') args: DeepPartial<UpvotePostInput>) {
return args.postId;
}

Related

Remove null results from a array that can contain nullable values in GraphQL

I have a query in my app that works but response is little ugly, there is probably two ways to solve this:
Write resolver differently
Clean response from null values
Here is resolver:
t.list.field('manyDevices', {
type: 'Device',
description: 'Get list of devices belonging to user',
args: {
input: nonNull(deviceIdentifierInput.asArg()),
},
resolve: async (_, { input: { id } }, { prisma }) => {
return await prisma.device.findMany({ where: { userId: id } });
},
});
This resolver looks for all devices with provided id. Id can be mine and also can be from a some other user. Devices can be public and private, and I don't want to receive private devices except if they are mine.
const isDevicePublic = rule({ cache: 'strict' })(
async ({ isPublic }: Device) => {
if (!isPublic) {
return permissionErrors.noPermission;
}
return true;
},
);
const isDeviceOwner = rule({ cache: 'strict' })(
async ({ userId }: Device, _, { user }: Context) => {
assertValue(user, permissionErrors.noAuthentication);
if (userId !== user.id) {
return permissionErrors.noPermission;
}
return true;
},
);
These are rules that I place on my schema with graphql-shield library and it works. There is just one problem, if a user have a private device it will be listed in response array as null and graphql-shield will throw error, so response can look like this:
{
"errors": [
{
"message": "You have no permission to access this resource",
"locations": [
{
"line": 3,
"column": 5
}
],
"path": [
"manyDevices",
0,
"name"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"stacktrace": [
"Error: You have no permission to access this resource",
" at Rule.resolve (/workspace/node_modules/graphql-shield/dist/rules.js:33:24)",
" at runMicrotasks (<anonymous>)",
" at processTicksAndRejections (internal/process/task_queues.js:93:5)",
" at async Promise.all (index 0)"
]
}
}
}
],
"data": {
"manyDevices": [
null,
{
"name": "device-01"
}
]
}
}
So there is one fetched device and other that is private that throws this error, can I somehow remove null and error response or should I filter them out in resolver?

Cannot pass custom result from resolver to Graphql

I am trying to fetch data with sequelize with an attribute and pass it to graphql.
The result is fine in console but the graphql query is returning null for the attribute field.
my resolver
getUnpayedLessons: async (_, args, { models }) => {
const { Attendance, Student } = models;
return await Attendance.findAll({
include: {
model: Student,
},
where: {
fk_lessonsSerieId: { [Op.is]: null },
},
attributes: ["id", [sequelize.fn("count", sequelize.col("absenceFlag")), "unpayedLessons"]],
group: ["student.id"],
});
},
query
getUnpayedLessons {
id
unpayedLessons
student {
id
firstName
lastName
}
}
schema
type UnpayedLessons {
id: Int
unpayedLessons: Int
student: Student
}
extend type Query {
getUnpayedLessons: [UnpayedLessons]
}
and this is the console.log of the resolver when I run the query
[
attendance {
dataValues: { id: 2, unpayedLessons: 8, student: [student] },
_previousDataValues: { id: 2, unpayedLessons: 8, student: [student] },
_changed: Set {},
_options: {
isNewRecord: false,
_schema: null,
_schemaDelimiter: '',
include: [Array],
includeNames: [Array],
includeMap: [Object],
includeValidated: true,
attributes: [Array],
raw: true
},
]
and from graphql
{
"data": {
"getUnpayedLessons": [
{
"id": 2,
"unpayedLessons": null,
"student": {
"id": 2,
"__typename": "Student"
},
"__typename": "UnpayedLessons"
},
]
}
}
Any idea how I can have unpayedLessons passed to graphql?
To debug this you need to check what is returned from DB, the shape:
const values = await Attendance.findAll({...
console.log( values );
// adapt structure to match query requirements
// finally return
return values;

graphql with typescript - #FieldResolver not working in compiled-to-js type-graphql

I a newbie with type-graphql and I have this weird issue of field resolvers not working.
I'm using a stack of:
typescript(3.9.5)
type-graphql(1.0.0)
typeorm(0.2.25)
apollo(2.16.0).
Whenever I run my typescript (using ts-node) everything works well, but when I use the compiled js version with express, the queries work only with simple db fields, but fieldResolvers don't work at all and in the end I get "Cannot return null for non-nullable field".
The funny thing is that the field resolvers are sibling of query resolvers in the same #Resolver class. the queries get fired (placed a console log) but the field resolvers never do. anyone have a clue?
here are some snippets from the code:
#Entity()
#ObjectType()
export class Member extends BaseEntity {
#Field(() => ID)
#PrimaryGeneratedColumn()
id: string;
#UseForSearch()
#UseAsTitle()
#Field(() => String)
#IsEmail()
#Column({ type: 'varchar', unique: true })
email: string;
#Field(() => [Item])
#OneToMany(type => Item, item => item.member)
items?: Item[];
#OneToMany(type => Review, review => review.member)
reviews?: Review[];
#Field(() => Number)
itemsCount: number;
}
#Entity()
#ObjectType()
export class Item extends BaseEntity {
#Field(() => ID)
#PrimaryGeneratedColumn()
id: string;
#ManyToOne(type => Category, category => category.items)
#JoinColumn({ name: 'categoryId' })
#Field(() => Category)
category?: Category;
#Field(() => String, { nullable: true })
#Column({
nullable: true,
})
name: string;
#ManyToOne(type => Member, member => member.items)
#JoinColumn({ name: 'memberId' })
#Field(() => Member)
member?: Member;
#Field(() => ID)
#Column('int', { nullable: true })
public memberId: number | null;
}
import { Resolver, Query, FieldResolver, Root, Ctx, Arg } from 'type-graphql';
import { getRepository, Repository } from 'typeorm';
import { Item } from '../entity';
import { Member } from '../entity/member';
#Resolver(of => Member)
export class MemberResolver {
constructor(private itemsRepository: Repository<Item>) {
this.itemsRepository = getRepository(Item);
}
#Query(() => [Member])
members() {
console.log('in members query');
return Member.find();
}
#FieldResolver()
async items(#Root() member: Member) {
return await this.itemsRepository.find({ where: { memberId: member.id } });
}
#FieldResolver()
async itemsCount(#Root() member: Member) {
console.log('in itemsCount resolver');
return this.itemsRepository.count({ where: { memberId: member.id } });
}
query {
members {
email
itemsCount
}
}
{
"errors": [
{
"message": "Cannot return null for non-nullable field Member.itemsCount.",
"locations": [
{
"line": 4,
"column": 5
}
],
"path": [
"members",
0,
"itemsCount"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"stacktrace": [
"Error: Cannot return null for non-nullable field Member.itemsCount.",
" at completeValue (/Users/admin/tackar/clampa/clampa-server/node_modules/graphql/execution/execute.js:599:13)",
" at completeValueCatchingError (/Users/admin/tackar/clampa/clampa-server/node_modules/graphql/execution/execute.js:534:19)",
" at resolveField (/Users/admin/tackar/clampa/clampa-server/node_modules/graphql/execution/execute.js:465:10)",
" at executeFields (/Users/admin/tackar/clampa/clampa-server/node_modules/graphql/execution/execute.js:297:18)",
" at collectAndExecuteSubfields (/Users/admin/tackar/clampa/clampa-server/node_modules/graphql/execution/execute.js:752:10)",
" at completeObjectValue (/Users/admin/tackar/clampa/clampa-server/node_modules/graphql/execution/execute.js:742:10)",
" at completeValue (/Users/admin/tackar/clampa/clampa-server/node_modules/graphql/execution/execute.js:630:12)",
" at completeValue (/Users/admin/tackar/clampa/clampa-server/node_modules/graphql/execution/execute.js:596:21)",
" at completeValueCatchingError (/Users/admin/tackar/clampa/clampa-server/node_modules/graphql/execution/execute.js:534:19)",
" at /Users/admin/tackar/clampa/clampa-server/node_modules/graphql/execution/execute.js:655:25"
]
}
}
}
],
"data": null
}
There is an issue with your compile step (TS to JS) - if it doesn't emit ES6 classes and arrow functions, it will treat of => Member as a function with prototype, so it gonna think it's the object type class.
The problem is not the compiling. The problem is your data
the message you are seeing is Cannot return null for non-nullable field Member.itemsCount
Well, either you add a value in itemsCount, or mark the field as nullable. And the problem is you are missing an await before the call to members count
Just add the await
#FieldResolver()
async itemsCount(#Root() member: Member) {
console.log('in itemsCount resolver');
return await this.itemsRepository.count({ where: { memberId: member.id } });
}

Graphql returning Cannot return null for non-nullable field Query.getDate. As I am new to graphql I want to know is my approach is wrong or my code?

I have created resolver, schema and handler which will fetch some record from dynamoDB. Now when I perform query then I am getting "Cannot return null for non-nullable field Query.getDate" error. I would like to know whether my approach is wrong or there is any change required in code.
My code : https://gist.github.com/vivek-chavan/95e7450ff73c8382a48fb5e6a5b96025
Input to lambda :
{
"query": "query getDate {\r\n getDate(id: \"0f92fa40-8036-11e8-b106-952d7c9eb822#eu-west-1:ba1c96e7-92ff-4d63-879a-93d5e397b18a\") {\r\n id\r\n transaction_date\r\n }\r\n }"
}
Response :
{
"errors": [
{
"message": "Cannot return null for non-nullable field Query.getDate.",
"locations": [
{
"line": 2,
"column": 7
}
],
"path": [
"getDate"
]
}
],
"data": null
}
Logs of lambda function :
[ { Error: Cannot return null for non-nullable field Query.getDate.
at completeValue (/var/task/node_modules/graphql/execution/execute.js:568:13)
at completeValueCatchingError (/var/task/node_modules/graphql/execution/execute.js:503:19)
at resolveField (/var/task/node_modules/graphql/execution/execute.js:447:10)
at executeFields (/var/task/node_modules/graphql/execution/execute.js:293:18)
at executeOperation (/var/task/node_modules/graphql/execution/execute.js:237:122)
at executeImpl (/var/task/node_modules/graphql/execution/execute.js:85:14)
at execute (/var/task/node_modules/graphql/execution/execute.js:62:229)
at graphqlImpl (/var/task/node_modules/graphql/graphql.js:86:31)
at /var/task/node_modules/graphql/graphql.js:32:223
at graphql (/var/task/node_modules/graphql/graphql.js:30:10)
message: 'Cannot return null for non-nullable field Query.getDate.',
locations: [Object],
path: [Object] } ],
data: null }
2019-02-25T10:07:16.340Z 9f75d1ea-2659-490b-ba59-5289a5d18d73 { Item:
{ model: 'g5',
transaction_date: '2018-07-05T09:30:31.391Z',
id: '0f92fa40-8036-11e8-b106-952d7c9eb822#eu-west-1:ba1c96e7-92ff-4d63-879a-93d5e397b18a',
make: 'moto' } }
Thanks in advance!
This is your code:
const data = {
getDate(args) {
var params = {
TableName: 'delete_this',
Key: {
"id": args.id
}
};
client.get(params, function(err,data){
if(err){
console.log('error occured '+err)
}else{
console.log(data)
}
});
},
};
const resolvers = {
Query: {
getDate: (root, args) => data.getDate(args),
},
};
You're seeing that error because getDate is a a Non-Null field in your schema, but it is resolving to null. Your resolver needs to return either a value of the appropriate type, or a Promise that will resolve to that value. If you change data like this
const data = {
getDate(args) {
return {
id: 'someString',
transaction_date: 'someString',
}
}
}
you'll see the error go away. Of course, your goal is to return data from your database, so we need to add that code back in. However, your existing code utilizes a callback. Anything you do inside the callback is irrelevant because it's ran after your resolver function returns. So we need to use a Promise instead.
While you can wrap a callback with Promise, that shouldn't be necessary with aws-sdk since newer versions support Promises. Something like this should be sufficient:
const data = {
getDate(args) {
const params = //...
// must return the resulting Promise here
return client.get(params).promise().then(result => {
return {
// id and transaction_date based on result
}
})
}
}
Or using async/await syntax:
const data = {
async getDate(args) {
const params = //...
const result = await client.get(params).promise()
return {
// id and transaction_date based on result
}
}
}

how to return customize data in prisma subscription

I am learning graphql & prisma and I came across a question on prisma subscription.
I want to return an item list whenever there is a creation or update on Item. So this is my code which not works.
scheme.graphql
# import Item from "./generated/prisma.graphql"
type Subscription {
todoItems: TodoItems
}
type TodoItems {
items: [Item!]!
}
resolver
const Subscription = {
todoItems: {
subscribe: async (parent, args, context, info) => {
const itemSubscription = await context.db.subscription.item({
where: { mutation_in: ['CREATED', 'UPDATED'] },
}, info);
return itemSubscription;
},
resolve: async (payload, args, context, info) => {
const items = await context.db.query.items({ type: 0, orderBy: 'updatedAt_DESC' }, info);
return { items };
},
},
}
module.exports = {
Subscription,
}
and in graphql playground,
subscription{
todoItems{
items{
title
}
}
}
it gives the error:
{
"errors": [
{
"message": "Anonymous Subscription must select only one top level field.",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"todoItems"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"stacktrace": [
"Error: Anonymous Subscription must select only one top level field.",
" at asErrorInstance (d:\\git\\inote\\node_modules\\graphql\\execution\\execute.js:489:43)",
" at <anonymous>",
" at process._tickCallback (internal/process/next_tick.js:118:7)"
]
}
}
}
]
}
Any idea?
Prisma does not support subscribing item lists. Instead, prisma wants you to subscribe to single item mutations ("created", "updated", "deleted"). As described here.
E.g.
subscription newTodos {
todo(where: {
mutation_in: [CREATED]
}) {
mutation
node {
title
}
}
}
To get "the full list", you have to query on the todos after subscribing to avoid missing events (race condition). As a result you have to manually "sync" the data from the subscription and your query.

Resources