Cannot return null for non-nullable field, but the field is nullable - graphql

I'm trying to create a query which have to return a list of users from my app. The problem is that the users list can be null, as I specified in the returned type for the query:
#ObjectType()
export class UsersListResponse {
#Field(() => [FieldError], { nullable: true })
errors?: FieldError[];
#Field(() => [User], { nullable: true })
users?: User[]
#Field(() => Boolean, { nullable: true })
hasMore?: boolean
}
The query is:
async getUsersList(
#Ctx() { req }: AppContext,
#Arg("offset", { nullable: true }) offset?: number,
#Arg("limit", { nullable: true }) limit?: number,
#Arg("orderBy", { nullable: true }) orderBy?: string,
#Arg("orderType", { nullable: true }) orderType?: string,
): Promise<UsersListResponse | null> {
// code
}
When the users list is null, I receive this error:
Cannot return null for non-nullable field UsersListResponse.users.

Related

Resolve one to many relation Nest.js / GraphQL Federation with Mercurius

I am using Nest.js / GraphQL Federation with Mercurius. I have two entities Products and Store that reside in different apps. The store should have multiple products as one to many relationship. I have a StoresQueries resolver that has a method findAll that returns a StoresPagination object. Each returned store object does not have the fields mentioned in the products app from the apps/products/src/products/resolvers/Store.resolver.ts and same apps/products/src/products/models/Store.model.ts I want to keep stuff decoupled and more or less the same structure works fine on other entities.
Project structure
apps/
store/
src/
store/
models/
Store.model.ts
resolvers/
Store.resolver.ts
StoresQueries.resolver.ts
services/
Store.service.ts
Store.module.ts
App.module.ts
main.ts
products/
src/
store/
models/
Store.model.ts
Product.model.ts
resolvers/
Store.resolver.ts
Product.resolver.ts
services/
Product.service.ts
Product.module.ts
App.module.ts
main.ts
apps/store/src/models/Store.model.ts
#ObjectType()
#Directive('#key(fields: "id")')
export class Store {
#Field(() => ID)
id: string;
#Field()
#TextSearch()
name: string;
}
apps/store/src/resolvers/Store.resolver.ts
#Resolver(() => Store)
export class StoreResolver {
constructor(private readonly storeService: StoreService) {}
#Query(() => StoresQueries, { name: 'stores' })
async storesQueries(): Promise<StoresQueries> {
return {};
}
#ResolveReference()
async resolveReference(reference: {
__typename: string;
id: string;
}): Promise<Store> {
return this.storeService.findById(reference.id);
}
}
apps/store/src/resolvers/StoresQueries.resolver.ts
#Resolver(() => StoresQueries)
export class StoresQueriesResolver {
constructor(private storeService: StoreService) {}
#ResolveField(() => StorePagination)
async findAll(
#CurrentUser() user: JwtUser,
#Args('input', { nullable: true }) input: PaginationInput | null,
): Promise<StorePagination> {
return this.storeService.findAll(input?.cursor, input?.limit);
}
}
app/products/src/products/models/Store.model.ts
#ObjectType()
#Directive('#key(fields: "id")')
#Directive('#extends')
export class Store {
#Directive('#external')
#Field(() => ID)
id: string;
// these both fields are not shown when querying StoresQueries for each store
#Field(() => ProductsPagination, { nullable: true })
products?: ProductsPagination;
#Field(() => Number, { nullable: true })
dummyCount?: number;
}
app/products/src/products/models/Product.model.ts
#ObjectType()
#Directive('#key(fields: "id")')
#Label('product', ['id'])
export class Product {
#Field(() => ID)
id: string;
}
app/products/src/products/resolvers/Product.resolver.ts
#Resolver(() => Product)
export class ProductsResolver {
constructor(
private productsService: ProductsService,
) {}
#ResolveReference()
async resolveReference(reference: {
__typename: string;
id: string;
}): Promise<Product> {
return this.productsService.findById(reference.id);
}
}
app/products/src/products/resolvers/Store.resolver.ts
#Resolver(() => Store)
export class StoreResolver {
constructor(
private productsService: ProductsService,
) {}
#ResolveField(() => ProductsPagination, { nullable: true })
async products(
#Parent() parent: Store,
#Args('input', { nullable: true }) input: PaginationInput | null,
): Promise<ProductsPagination> {
const wp = this.productsService.getProductsByStoreId(
parent.id,
kind,
input,
);
return { __typename: 'ProductsPagination', ...wp };
}
}

Cannot return null for non-nullable field - Typegoose and TypeGraphQL

I have this Album model set up using Typegoose and TypeGraphQL, which contains multiple songs from the Song model using the AlbumSong ObjectType:
import {
prop as Property,
getModelForClass,
modelOptions,
Ref,
} from "#typegoose/typegoose";
import { Field, ObjectType, ID } from "type-graphql";
import { AlbumCategory, } from "./albumCategory.model";
import { Song } from "./song.model";
#ObjectType()
export class AlbumSong {
#Field(() => ID)
#Property({ required: true })
id!: string;
#Field(() => Song)
#Property({ type: () => Song, ref: () => Song, required: true })
song!: Song;
}
#ObjectType({ description: "The Album Model" })
#modelOptions({ schemaOptions: { collection: "albums", timestamps: true } })
export class Album {
#Field(() => ID)
id: string;
#Field()
#Property({ type: () => String })
title: string;
#Field(() => [AlbumSong])
#Property({ type: AlbumSong })
albumSongs!: Partial<AlbumSong>[];
#Field()
#Property({ required: true, default: Date.now })
createdAt: Date;
#Field()
#Property({ required: true, default: Date.now })
updatedAt: Date;
}
export const AlbumModel = getModelForClass(Album);
When trying to query the album using:
#Query((_returns) => Album, { nullable: false, name: "album" })
async getAlbumById(#Arg("id") id: string) {
return await AlbumModel.findById({ _id: id });
}
With the following GraphQL:
query Album($albumId: String!) {
album(id: $albumId) {
id
albumSongs {
id
song {
id
}
}
}
}
I get: "Cannot return null for non-nullable field AlbumSong.song."
To me it seems like the reference is not working, when i only query the albumSong's id it returns just fine...
Setup a FieldResolver to resolve the song within an AlbumSong
#Resolver(() => AlbumSong)
export class AlbumSongFieldResolver {
#FieldResolver(() => Song)
async song(#Root() parent: AlbumSong): Promise<Song> {
return Song.findOne(parent.song);
}
}

how I can add a custom scalar with different formats in type-graphql to generate GraphQL schema

I tried to generate a GraphQl schema using type-graphql lib I want the field value to accept any data type but it only takes a string. I have created a custom scalar with the name SettingValueScalar but it's not working as well
import { ObjectID } from 'mongodb'
import 'reflect-metadata'
import { Field, InputType, Int, ObjectType } from 'type-graphql'
import { DateScalar } from '../scalars/date.scalar'
import { SettingValueScalar } from '../scalars/setting-value.scalar'
#ObjectType()
#InputType('SettingInput')
export class Setting implements MSetting {
readonly _id!: ObjectID
#Field(() => Int)
id!: number
#Field({ nullable: true })
slug?: string
#Field({ nullable: true })
domain?: string
#Field(()=>SettingValueScalar,{ nullable: true })
value?: string| number | string[] | number[]
#Field(() => DateScalar, { nullable: true })
createdAt?: Date
#Field(() => DateScalar, { nullable: true })
updatedAt?: Date
}
SettingValueScalar
import { GraphQLScalarType, Kind } from 'graphql'
export const SettingValueScalar = new GraphQLScalarType({
name: 'SettingValue',
description: 'Setting value scalar type',
serialize(value: any): any {
return value
},
parseValue(value: any): any {
return value
},
parseLiteral(ast) {
return last
},
})

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

Prisma cannot create a user type because of array argument?

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

Resources