Closed danpaugo closed 6 years ago
This error says that the isTypeOf
check failed. That happened because in TypeGraphQL it's implemented using instanceOf
operator, so you need to return an instance of your object type class.
This requirement takes place when the return type is union or your object types implements GraphQL interfaces or extends other classes. Technically your not implementing gql interfaces nor extend other object type class but you are extending Typegoose
. Unfortunately this is a limitation of current schema generation using graphql-js
.
@19majkel94 thanks a lot !!! Currently migrating our app to type-graphql and this kinda came in our way. Any recommended workarounds ? Or any examples on how to use typegoose with type-graphql
Hard to say. typegoose
is badly written and has a lot of flaws.
For now you just need to manually convert plain object to class instance using class-transformer
or Object.setPrototypeOf
.
You may also use getClassForDocument
helper method in Typegoose that I've made and then write a TypeGraphQL middleware that will automatically make the conversion if the result has _id
field or sth.
Oh ok cool. Thanks a lot for your time!
Hi, I use typegroose and your amazing typegraphql To covert plain text to graphql type I used class-transformer, but I have this error: Expected value of type \"UserGraphQL\" but got: class UserGraphQL extends typegoose_1.Typegoose {\n}.
@ObjectType() class UserGraphQL extends Typegoose
Your check, isTypeOf, doesn't support extended class?
Your check, isTypeOf, doesn't support extended class?
It supports extending classes: https://github.com/19majkel94/type-graphql/blob/200235dc31c3fd26274eee4aacf11d5f04477d56/tests/functional/interfaces-and-inheritance.ts#L466 https://github.com/19majkel94/type-graphql/blob/200235dc31c3fd26274eee4aacf11d5f04477d56/tests/functional/interfaces-and-inheritance.ts#L599
It's hard to foretell from this small amount of information - if you are 100% sure that you return correct instances of object type classes, please create a minimal reproducible code example.
@19majkel94 Thank you for your job!
Please, help me to convert my object to class instance
@ObjectType()
class AdminSchema extends Typegoose {
@Field()
@prop()
login?: string;
}
@Resolver(AdminSchema)
export default class AdminResolver {
@Query(returns => AdminSchema)
admin() {
return Admin.findOne({ login: 'admin' })
}
}
const Admin = new AdminSchema().getModelForClass(AdminSchema, {
schemaOptions: { collection: 'admin' }
});
When I try to query "admin": { admin { login } }
I've got:
"Expected value of type \"AdminSchema\" but got: { _id: { _bsontype: \"ObjectID\", id: <Buffer 5c 5f >0a b0 f0 48 6c 01 57 99 c1 f1> }, login: \"admin\" }."
When I modify my Query to:
@Query(returns => AdminSchema)
admin() {
return Object.setPrototypeOf(Admin.findOne({ login: 'admin' }), AdminSchema)
}
I've got
"Maximum call stack size exceeded"
When I modify my Query to:
@Query(returns => AdminSchema)
admin() {
return plainToClass(Admin.findOne({ login: 'admin' }), AdminSchema)
}
I've got
"Expected value of type \"AdminSchema\" but got: [function AdminSchema]."
and warning:
Argument of type 'DocumentQuery<InstanceType
| null, >InstanceType , {}>' is not assignable to parameter of type 'ClassType<{}>'. Type 'DocumentQuery<InstanceType | null, InstanceType , {}>' >provides no match for the signature 'new (...args: any[]): {}'
@danpaugo
Try .toObject()
first, before conversion (Object.setPrototypeOf
):
https://mongoosejs.com/docs/guide.html#toObject
@19majkel94 Please, if it possible, give me advice. How to use type-graphql and vanilla mongoose (without typegoose) to avoid declare schemas twice (new mongoose.Schema and new GraphQLObjectType Fields) Thanks a lot
@andreyvolfman It's not possible unless you write your own library to transform mongoose schema to TypeScript classes with decorators.
The goal of TypeGraphQL is the opposite - declare the class, have side effect (graphql schema, mongoose schema, sql table) by using decorators.
@19majkel94 Thank you for answer!
Hard to say.
typegoose
is badly written and has a lot of flaws. For now you just need to manually convert plain object to class instance usingclass-transformer
orObject.setPrototypeOf
. You may also usegetClassForDocument
helper method in Typegoose that I've made and then write a TypeGraphQL middleware that will automatically make the conversion if the result has_id
field or sth.
Can you provide an example with getClassForDocument
please...
Hi, I have problems to make this to work some help is aprreciated thanx @19majkel94 Model
import { prop, Typegoose } from 'typegoose';
import { ObjectType, Field, ID } from 'type-graphql';
@ObjectType()
export class User extends Typegoose {
@Field(() => ID)
_id: string;
@prop({ required: true })
@Field()
fullName: string;
@prop({ required: true })
@Field()
email: string;
@prop({ required: true })
@Field()
password: string;
@Field()
@prop({ required: true })
terms: boolean;
}
export const UserModel = new User().getModelForClass(User, { schemaOptions: { timestamps: true } });
Resolver
@Resolver(() => User)
export class RegisterResolver {
@Query(() => String)
async hello() {
return "Hello World!";
}
@Mutation(() => User)
async register(
@Arg("fullName") fullName: string,
@Arg("email") email: string,
@Arg("password") password: string,
@Arg("terms") terms: boolean
): Promise<User> {
const hashedPassword = await bcrypt.hash(password, 12);
const user = new UserModel({
fullName: fullName,
email: email,
password: hashedPassword,
terms: terms
});
const createdUser = await user.save();
if (!createdUser) {
throw new UserInputError(`User not found`);
}
const converted = Object.setPrototypeOf(createdUser, User);
console.log(converted);
const userReflectedType = getClassForDocument(createdUser);
console.log('TCL: RegisterResolver -> userReflectedType', userReflectedType)
return createdUser;
}
}
I am having same issue as above
const createdUserDoc = await user.save();
const createdUserObj = createdUserDoc.toObject();
Object.setPrototypeOf(createdUserObj, User);
return createdUserObj;
You can reduce this boilerplate by using a middleware that will check if the returned object is a mongoose document, convert it to plain JS object, detect the underlying class using getClassForDocument
and set the prototype of the object.
In 0.17.1
all you would have to do is to return createdUser.toObject()
(or use a middleware for that) thanks to #279 🎉
I still am having problems getting my side to work, typegoose Along with type-graphql..
I continue to get this error, even if i follow above sugggestion. very frustrating. i have made my repo public under 'tradie' if anyone can spare some time to help.
"message": "Expected value of type \"User\" but got: { _id: { _bsontype: \"ObjectID\", id: <Buffer 5c 96 42 03 db 0a 01 2c 28 a9 bb 5a> }, firstName: \"Ryann\", lastName: \"Galea\", email: \"ryann@motoduel.com\", password: \"$2a$12$N0.i7JLKa6Zl.W1mLoYNqOmyKzb7oJWLZ9dm6O7nTxQctkkzgJ4gq\", __v: 0 }."
@RyannGalea
_id: { _bsontype:
means no .toObject()
has been called by you
@19majkel94 Your solution does not work, same error that @RyannGalea mentioned above, My new code
const createdUser = await user.save();
if (!createdUser) {
throw new UserInputError(`User not found`);
}
const createdUserObj = createdUser.toObject();
console.log('TCL: RegisterResolver -> toObject', createdUserObj)
Object.setPrototypeOf(createdUserObj, User);
const userReflectedType = getClassForDocument(createdUserObj);
console.log('TCL: RegisterResolver -> userReflectedType', userReflectedType)
return createdUserObj;
Logs: TCL: RegisterResolver -> toObject { _id: 5c96dfccbfd6092584eef64d, fullName: 'Anthony Willis', email: 'test@test.com', password: '$2a$12$5vle3c9xnDKkEc9EUZeNv.GrReaAAlgsWahYY8XibtTFp/PN8FK2S', terms: true, createdAt: 2019-03-24T01:39:24.287Z, updatedAt: 2019-03-24T01:39:24.287Z, __v: 0 } TCL: RegisterResolver -> userReflectedType undefined
Anyways Im checking out nestjs with your type-graphql + graphql that seems like work corretly thanks!
Ok, thanks for that, I have made some changes to make sure my '_id' is a string, Here are my files below. I am returning a definite 'User' shape object from my register function but I continue to get this error in graphql. What am I missing here? usually in other projects this would work.
User.model.ts
import { prop, Typegoose } from 'typegoose';
import { ObjectType, Field, Root, ID } from 'type-graphql';
@ObjectType()
export class User extends Typegoose {
@Field({nullable: true})
readonly _id: string;
@Field({nullable: true})
@prop({required: true})
firstName: string;
@Field({nullable: true})
@prop()
lastName: string;
@Field({nullable: true})
@prop({required: true, unique: true, lowercase: true})
email: string;
@prop({required: true})
password: string;
}
const schemaOptions = {
toObject: {
transform: (doc, ret, options) => {
delete ret.__v
ret._id = ret._id.toString()
return ret;
}
}
};
export const UserModel = new User().getModelForClass(User, {schemaOptions});
User.resolvers.ts
import bcrypt from 'bcryptjs';
import * as _ from 'lodash';
import { UserModel, User } from '../../model/user/User.model';
import { Resolver, Query, Mutation, Arg } from 'type-graphql';
import { RegisterInput } from './Register.input';
@Resolver()
export class UserResolver {
@Query(() => String)
usertest() {
return 'Hello World';
}
@Mutation(() => User)
async register(
@Arg('input') {firstName, lastName, email, password}: RegisterInput
): Promise<User> {
password = await bcrypt.hashSync(password, 12);
const user = await new UserModel({firstName, lastName, email, password }).save();
const obj = _.pick(user.toObject(), ['_id', 'firstName', 'lastName', 'email'])
return obj;
}
}
Grahpql Query
mutation {
register(
input: {
firstName: "Ryann"
lastName: "Galea"
email: "ryann@motoduel.com"
password: "narly"
}
) {
_id
firstName
lastName
email
}
}
Graphql Error
{
"errors": [
{
"message": "Expected value of type \"User\" but got: { _id: \"5c96df8d67b69756acadc04d\", firstName: \"Ryann\", lastName: \"Galea\", email: \"ryann@motoduel.com\" }.",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"register"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"message": "Expected value of type \"User\" but got: { _id: \"5c96df8d67b69756acadc04d\", firstName: \"Ryann\", lastName: \"Galea\", email: \"ryann@motoduel.com\" }.",
"locations": [
{
"line": 2,
"column": 3
}
],
"stacktrace": [
"GraphQLError: Expected value of type \"User\" but got: { _id: \"5c96df8d67b69756acadc04d\", firstName: \"Ryann\", lastName: \"Galea\", email: \"ryann@motoduel.com\" }.",
" at invalidReturnTypeError (C:\\tradie\\server\\node_modules\\graphql\\execution\\execute.js:711:10)",
" at completeObjectValue (C:\\tradie\\server\\node_modules\\graphql\\execution\\execute.js:703:13)",
" at completeValue (C:\\tradie\\server\\node_modules\\graphql\\execution\\execute.js:598:12)",
" at completeValue (C:\\tradie\\server\\node_modules\\graphql\\execution\\execute.js:565:21)",
" at C:\\tradie\\server\\node_modules\\graphql\\execution\\execute.js:500:16",
" at process._tickCallback (internal/process/next_tick.js:68:7)"
]
}
}
}
],
"data": null
}
@RyannGalea Like you I try that too and same error. If it hurry to you give a try nestjs, it integrate mongo + typegraphql + moongose perfectly
I'm guessing it's because the User class extends Typegoose, but I do not know how to remedy this.
If you can check my code above @RyannGalea , getClassForDocument return undefined before use toObject() that return User class extends Typegoose, so I guess you are right but @19majkel94 said and the test that showed that @ObjectType () supports extends, so I'm not sure if it's because of typegoose or that objectType acts strangely with typegoose. Im very confusing with this.
Such a clean setup I have at the moment I really want to use this current stack I have implemented and not learn nestjs at the moment.
OK @anthowm,
Heres some working code, once I copy out my returned mongoose object using lodash 'pick', I'm then able to 'setPrototypeOf' to my desired class then return it and it works.
If I try and 'setPrototypeOf' on my returned mongoose Object I cannot make it work, I have to extract it out first to a new object.
Also, because I am copying the data into a new object I was able to remove the 'schemaOptions' in the model, and remove .toObject() in my mutation.
User.model.ts
import { prop, Typegoose } from 'typegoose';
import { ObjectType, Field, Root, ID } from 'type-graphql';
@ObjectType()
export class User extends Typegoose {
@Field()
readonly _id: string;
@Field()
@prop({required: true})
firstName: string;
@Field()
@prop()
lastName: string;
@Field()
@prop({required: true, unique: true, lowercase: true})
email: string;
@prop({required: true})
password: string;
}
export const UserModel = new User().getModelForClass(User);
User.resolver.ts
import bcrypt from 'bcryptjs';
import * as _ from 'lodash';
import { UserModel, User } from '../../model/user/User.model';
import { Resolver, Query, Mutation, Arg } from 'type-graphql';
import { RegisterInput } from './Register.input';
@Resolver()
export class UserResolver {
@Query(() => String)
usertest() {
return 'Hello World';
}
@Mutation(() => User)
async register(
@Arg('input') {firstName, lastName, email, password}: RegisterInput
): Promise<User> {
password = await bcrypt.hashSync(password, 12);
const user = await new UserModel({firstName, lastName, email, password }).save();
const obj = _.pick(user, ['_id', 'firstName', 'lastName', 'email'])
return Object.setPrototypeOf(obj, new User);
}
}
Without lodash...
import bcrypt from 'bcryptjs';
import { UserModel, User } from '../../model/user/user.model';
import { Resolver, Query, Mutation, Arg } from 'type-graphql';
import { RegisterInput } from './register.input';
@Resolver()
export class UserResolver {
@Query(() => String)
usertest() {
return 'Hello World';
}
@Mutation(() => User)
async register(@Arg('input') input: RegisterInput): Promise<User> {
input.password = await bcrypt.hashSync(input.password, 12);
const { _id, firstName, lastName, email, mobile } = await new UserModel({...input}).save();
return Object.setPrototypeOf({ _id, firstName, lastName, email, mobile }, new User);
}
}
I hope this example will close the integration topic and discussions forever 😉
https://github.com/MichalLytek/type-graphql/tree/master/examples/typegoose
@19majkel94 great work dude. thanks! @RyannGalea great work too!
Thankyou @19majkel94 for taking the time to write that repo for us.
@19majkel94 thanks for you hard work! It's absolutely fantastic that there's a good way to do this, without using toObject() everywhere. Your solution is working fantastically, untill I add populate. Then it's broken again. Is there a good way to fix it? Right now I am using an ugly hack within TypegooseMiddleware function from your example. It works, but I wonder if there's a better way to do this. Right now it looks like this after the check:
const items = convertedDocument.items.map((el: ItemClass) => {
return Object.setPrototypeOf(el, new ItemClass())
})
convertedDocument.items = items
@JohnatanNorman as said, in next release (0.17.1) it should be fixed thanks to #279. The prototype fix will be needed only when you return an interface or an union.
Unfortunately Typegoose is only a wrapper on mongoose, it doesn't support classes naturally like TypeORM.
And the GraphQL resolvers architecture is about building reusable field resolvers, not reading the info
and doing sql joins or mongoose populate to return nested data.
Hello, I have a question about your example with the typegoose integretion. If i follow your example I have an error.
My version of typegoose is newer than the example. When I have solved my problem, I will propose a pull request to update the example if you want.
demand.type.ts
import { Field, ObjectType } from 'type-graphql';
import { getModelForClass, prop as Property } from '@typegoose/typegoose';
import { ObjectIdScalar } from '../../scalars/objectId.scalar';
import { ObjectId } from 'mongodb';
@ObjectType()
export class Demand {
@Field(type => ObjectIdScalar)
readonly _id: ObjectId;
@Field()
@Property({ required: true })
name: string;
}
export const DemandModel = getModelForClass(Demand);
demand.input.ts
import { Field, InputType } from 'type-graphql';
import { Demand } from './demand.type';
@InputType()
export class DemandInput implements Partial<Demand> {
@Field()
name: string;
}
demand.resolver.ts
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
import { Demand, DemandModel } from './types/demand.type';
import { DemandInput } from './types/demand.input';
@Resolver(of => Demand)
export class DemandResolver {
@Query(returns => [Demand])
async demands(): Promise<Demand[]> {
return await DemandModel.find({});
}
@Mutation(returns => Demand)
async demandCreate(@Args('input') input: DemandInput): Promise<Demand> {
const createDemand = new DemandModel(input as Demand);
Logger.log(createDemand, 'createDemand');
return await createDemand.save();
}
}
the mutation I use
mutation createDemand{
demandCreate(input: {
name: "thanks for the help"
}){
_id
name
}
}
Log of the createDemand method of the resolver
{
"_id": "5e3944d85ea7c440cbb8f2e9",
"name": "thanks for the help"
}
My package.json
"@nestjs/common": "^6.7.2",
"@nestjs/core": "^6.7.2",
"@nestjs/graphql": "^6.5.3",
"@nestjs/platform-express": "^6.7.2",
"@typegoose/typegoose": "^6.2.1",
"apollo-server-express": "^2.9.16",
"graphql": "^14.5.8",
"graphql-tools": "^4.0.6",
"mongoose": "^5.8.7",
"nestjs-typegoose": "^7.0.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.0",
"rxjs": "^6.5.3"
error is
{
"error": "Failed to fetch. Please check your connection"
}
My connection works well because I have an another module which have @ObjectType and Typegoose model separate and it works well.
demand.model.ts
import { prop } from '@typegoose/typegoose';
export class DemandModel {
@prop()
name: string;
}
demand.type.ts
import {Field, ObjectType} from 'type-graphql';
import {ObjectIdScalar} from '../../scalars/objectId.scalar';
@ObjectType()
export class Demand {
@Field(type => ObjectIdScalar, { nullable: true })
_id?: string;
@Field({ nullable: true })
name?: string;
}
demand.input.ts
import { Field, InputType } from 'type-graphql';
@InputType()
export class DemandInput {
@Field()
readonly name: string;
}
demand.resolver.ts
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
import { Demand } from './types/demand.type';
import { DemandInput } from './types/demand.input';
import { DemandService } from './demand.service';
@Resolver(of => Demand)
export class DemandResolver {
constructor(private readonly demandService: DemandService) { }
@Mutation(() => Demand)
async demandCreate(@Args('input') input: DemandInput) {
return await this.demandService.create(input);
}
@Query(() => [Demand], { name: 'demands' })
async getDemands() {
return await this.demandService.findAll();
}
@Query(() => Demand, { name: 'demand' })
async getDemand(@Args('id') id: string) {
return await this.demandService.findOne(id);
}
}
demand.service.ts
import { Injectable } from '@nestjs/common';
import { DemandModel } from './models/demand.model';
import { InjectModel } from 'nestjs-typegoose';
import { ReturnModelType } from '@typegoose/typegoose';
@Injectable()
export class DemandService {
constructor(@InjectModel(DemandModel) private readonly demandModel: ReturnModelType<typeof DemandModel>) {}
async create( demand: DemandModel) {
const createDemand = new this.demandModel(demand);
return createDemand.save();
}
async findAll(): Promise<DemandModel[] | null> {
return this.demandModel.find().exec();
}
async findOne(id: string): Promise<DemandModel | null> {
return this.demandModel.findById(id);
}
// async update(id: string, demand: UserModel): Promise<UserModel> {
// return await this.demandModel.findByIdAndUpdate(id, demand, { new: true });
// }
//
// async delete(id: string): Promise<UserModel> {
// return await this.demandModel.findByIdAndRemove(id);
// }
}
I'm using nestjs to implement the graphql server but just before saving my model seems to be good. I'm sorry in advance not to leave this subject buried, but I've searched without finding an answer...
Failed to fetch. Please check your connection My connection works well
@oliviermattei I bet this is a modules and NestJS issue. I can't help you with that, if you can't provide a clear and simple reproductions that shows it's TypeGraphQL fault somehow.
@oliviermattei I've a problem with generate schema. This is issue Can you help me?
I hope this example will close the integration topic and discussions forever 😉
https://github.com/19majkel94/type-graphql/tree/master/examples/typegoose
Yes! Awesome docs
Hey guys, does anyone knows how to use multiple _doc in one entity/Model?
import { ObjectType, Field, ID } from "type-graphql";
import { prop as Property, getModelForClass } from "@typegoose/typegoose";
import { Ref } from "../types";
import { User } from "./User";
import { Item } from "./Item";
import { Table } from "./Table";
import { __Type } from "graphql";
@ObjectType({ description: "The Order model" })
export class Order {
@Field(() => ID)
id: string;
@Field(_type => String)
@Property({ ref: User, required: true })
user_id: Ref<User>;
_doc: any;
@Field(_type => String)
@Property({ ref: Item, required: true })
item_id: Ref<Item>;
//_doc: any; // This still doesn't work // No duplicate identifier _doc
@Field(_type => String)
@Property({ ref: Table, required: true })
table_id: Ref<Table>;
//_doc: any; // This still doesn't work // No duplicate identifier _doc
@Field()
@Property({ required: true })
orderNumber : number;
@Field()
@Property({ required: true })
date : Date;
@Field()
@Property({ required: true })
location : String;
@Field()
@Property({ required: true })
numberOfPeople : number;
}
export const OrderModel = getModelForClass(Order);
@bryancolin try
import { Ref } from '@typegoose/typegoose' <--- Ref import from typegoose.
@Field(_type => User)
@Property({ ref: 'User', required: true })
user: Ref<User> <--- Named for what you will be returning.
@Field(_type => Item)
@Property({ ref: 'Item', required: true })
item: Ref<Item>;
@Field(_type => Table)
@Property({ ref: 'Table', required: true })
table: Ref<Table>;
Remember the '@Field' is what graphql will expect to get back.
You still save only the _id using your @InputType()
Your input for example:
import { ObjectId } from 'mongodb'
@Field(type => ObjectId)
user: ObjectId
i'm trying to call the doc of item and table but it won't let me since it can have duplicate identifier of _doc
@bryancolin Maybe share the code of what exactly you are doing. What is working, what isn't working
@bryancolin Jump in the Typegoose Discord if you need more help, I might catch you in there https://discord.gg/Dvdzfp
I must miss something, but with that model, I got a nested array instead of simple array for versions. Should I write it differently to get the good result ? The strange thing is that I get the correct result if I remove the @Property decorator ! Thanks for your help...
@modelOptions({ options: { customName: 'items' } }) @ObjectType() export default class Product { @Field(type => [Version]) @Property() versions : Version[]; }
Solved : I didn't see the { type: ...} @Property() option !
I hope this example will close the integration topic and discussions forever 😉
https://github.com/19majkel94/type-graphql/tree/master/examples/typegoose
This is great. Thanks for the example.
I'd just like to point out that arrayProp
has been deprecated as of Typegoose 7.1.1. Only prop
is necessary now.
https://typegoose.github.io/typegoose/docs/decorators/arrayProp/
It will be removed after version 8.0 of Typegoose, making the example faulty. Not sure when 8.0 will be coming out, but I thought I'd point it out.
Scott
@smolinari Can you make a PR with that adjustment? I'm not using typegoose on my daily basis.
Hehehe... I had a feeling you'd be asking me that. I'll see what I can do. I'm not the most experienced Typegoose user either. :)
Scott
Thank you @MichalLytek
Hey @19majkel94 I am trying to use typegoose with type-graphql. Following are the relevant code snippets from my user.model.ts, user.service.ts and user.resolver.ts files. user.model.ts
user.service.ts
user.resolver.ts
Whenever I try to fire the user resolver I get the following error
My package versions are as follows: apollo-server: 2.0.0 apollo-server-express: 2.0.0 mongoose: 5.2.4 type-graphql: 0.12.3 typegoose: 5.2.1
I am sorry I had to post this issue in github and not gitter because of the size of my code.