TypeORM GraphQL nested query returns null - graphql

I am trying to use a GraphQL nested query to get information about project a and customer. I can get the information about project but I am unable to get the customer info.
How do I properly get the nested information?
Project Model
import { Entity, BaseEntity, PrimaryGeneratedColumn, Column, ManyToOne} from "typeorm";
import { ObjectType, Field, ID } from "type-graphql";
import {Customer} from "./Customer";
#Entity('project')
#ObjectType()
export class Project extends BaseEntity {
#Field(() => ID)
#PrimaryGeneratedColumn()
id: number;
#Field(() => String)
#Column()
name: string;
#Field(() => String)
#Column()
short_name: string;
#Field(() => String)
#Column()
description: string;
#Field(() => Customer, { nullable: true })
#ManyToOne(type => Customer, customer => customer.projects)
customer: Customer;
}
User Model
import { Entity, BaseEntity, PrimaryGeneratedColumn, Column, OneToMany, JoinTable } from "typeorm";
import { ObjectType, Field, ID } from "type-graphql";
import {Project} from "./Project";
import {ProjectUser} from "./Project_handled_by_user";
#Entity('customer')
#ObjectType()
export class Customer extends BaseEntity {
#Field(() => ID)
#PrimaryGeneratedColumn()
id: number;
#Field(() => String)
#Column()
name: string;
#Field(() => String)
#Column()
short_name: string;
#Field(() => String)
#Column()
active: string;
#Field(() => [Project], { nullable: true })
#OneToMany(type => Project, project => project.customer)
projects: Project[];
}
Project Resolver
import { Resolver, Query, Mutation, Arg } from "type-graphql";
import { Project } from "../models/Project";
#Resolver()
export class ProjectResolver {
#Query(() => [Project])
projects() {
return Project.find();
}
#Query(() => Project)
project(#Arg("id") id: string) {
return Project.findOne({ where: { id } });
}
}
For example, my query is:
{
project(id: "1"){
id
name
customer{
id
name
}
}
}
The query returns:
{
"data": {
"project": {
"id": "1",
"name": "KFS 5.1",
"customer": null
}
}
}
Thanks for help

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

Schema must contain uniquely named types named "Project"

I am creating a Apollo Graphql backend using type-orm. I create an entity called Project:
import { Field, ObjectType } from "type-graphql";
import { BaseEntity, Column, Entity, ObjectID, ObjectIdColumn } from "typeorm";
#ObjectType()
#Entity()
export class Project extends BaseEntity {
#Field(() => String)
#ObjectIdColumn()
id: ObjectID;
#Field()
#Column({ unique: true })
name!: string;
#Field()
#Column()
startDate!: Date;
#Field()
#Column({nullable: true})
endDate!: Date
#Field()
#Column({unique:true})
githubUrl: string;
}
and the resolver project:
import { Arg, Mutation, Query, Resolver } from 'type-graphql'
import {Project} from '../entities/project'
import {ProjectInput, ProjectResponse} from '../types/ProjectTypes'
#Resolver()
export class ProjectResolver {
#Query(() => [Project])
async getProjects(): Promise<Project[] | null> {
let projects = await Project.getRepository().find();
return projects;
}
#Mutation(() => ProjectResponse)
async createProject(
#Arg("input") input: ProjectInput
): Promise<ProjectResponse>{
let project : Project;
if(input.name == ""){
throw Error("Invalid input")
}
try{
project = await Project.create({
name: input.name,
startDate: input.startDate,
}).save();
}catch (error) {
if (error.code === 11000) {
return {
errors: [
{
field: "project",
message: "The project name is already in use",
},
],
};
} else return error;
}
return {project: project};
}
#Mutation(() => ProjectResponse)
async setProjectEndDate(
#Arg("projectId") projectId: string,
#Arg("endDate") endDate: Date
): Promise<ProjectResponse>{
let project = await Project.getRepository().findOne(projectId)
if(project){
if(project?.startDate > endDate){
return {
errors:[{
field:"EndDate",
message:"The end date must be a date after the start date of a project."
}]
}
}
project.endDate = endDate;
project.save();
}
return {
errors:[{
field:"Project",
message:"Project could not be found."
}]
}
}
}
this is the code of the 2 auxiliary classes for the input and response of the resolver:
#InputType()
export class ProjectInput{
#Field()
name: string
#Field()
startDate: Date
#Field(()=> Date,{nullable:true})
endDate?: Date | null
#Field(()=> String, {nullable:true})
githubUrl?: string
}
#ObjectType()
export class ProjectResponse{
#Field(() => [FieldError], { nullable: true })
errors?: FieldError[]
#Field(() => Project, { nullable: true })
project?: Project | null
}
this is the code I use to create the ApolloServer object:
const apolloServer = new ApolloServer({
introspection: true,
playground: true,
schema: await buildSchema({
resolvers: [ProjectResolver],
validate: false, // Disable default GraphQL errors
}),
context: ({ req, res }) => ({ req, res}), // Enables use of context (with request) in resolvers
})
And the error I get is the following:
Error: Schema must contain uniquely named types but contains multiple types named "Project".
at new GraphQLSchema (C:\Users\User\Desktop\UPV\Proyectos\Cv web\myweb-backend\node_modules\graphql\type\schema.js:194:15)
at Function.generateFromMetadataSync (C:\Users\User\Desktop\UPV\Proyectos\Cv web\myweb-backend\node_modules\type-graphql\dist\schema\schema-generator.js:31:32)
at Function.generateFromMetadata (C:\Users\User\Desktop\UPV\Proyectos\Cv web\myweb-backend\node_modules\type-graphql\dist\schema\schema-generator.js:16:29)
at Object.buildSchema (C:\Users\User\Desktop\UPV\Proyectos\Cv web\myweb-backend\node_modules\type-graphql\dist\utils\buildSchema.js:10:61)
at C:\Users\User\Desktop\UPV\Proyectos\Cv web\myweb-backend\dist\index.js:42:38
at Generator.next ()
at fulfilled (C:\Users\User\Desktop\UPV\Proyectos\Cv web\myweb-backend\dist\index.js:5:58)
at processTicksAndRejections (internal/process/task_queues.js:97:5)
I don't know what the problem is so I would appreciate your help
I have managed to solve the problem by changing the import of the class 'Project' made in the 'ProjectResolver' class.
Instead of:
import {Project} from '../entities/project'
Now looks like this:
import {Project} from '../../src/entities/project'

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

Resources