MichalLytek / type-graphql

Create GraphQL schema and resolvers with TypeScript, using classes and decorators!
https://typegraphql.com
MIT License
8.04k stars 677 forks source link

Integration with typegoose #117

Closed danpaugo closed 6 years ago

danpaugo commented 6 years ago

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

@ObjectType()
@pre<User>('save', async function () {
  var user = this;
  if (user.isModified('password')) {
    user.password = await bcrypt.hash(user.password, SALT_WORK_FACTOR)
  }
})
export class User extends Typegoose {
  @Field(() => ID)
  _id: string;

  @Field(() => Date)
  createdAt: Date;

  @Field(() => Date)
  updatedAt: Date;

  @prop({ required: true })
  @Field(() => String, { nullable: false })
  firstName: string;

  @prop({ required: true })
  @Field(() => String, { nullable: false })
  lastName: string;

  @Field(() => String, { nullable: false })
  @prop({ required: true })
  email: string;

  @prop({ required: true })
  password: string;
}

export const UserModel = new User().getModelForClass(User, { schemaOptions: { timestamps: true } });

user.service.ts

import { UserModel, User } from '../models/user.model';

export const getUserById = async (userId: string): Promise<User> => {
  const user: User = await UserModel.findOne({ _id: userId });
  if (!user) {
    throw new UserInputError(`User with userId ${userId} not found`);
  }
  user.password = null;
  delete user.password;
  return user;
};

user.resolver.ts

import { User } from '../models/user.model';
import { getUserById } from '../services/user.service';

@Resolver(() => User)
export class UserResolver {
  @Query(() => User, { nullable: false })
  @UseMiddleware(Auth)
  async user(@Arg('_id') _id: string, @Ctx() context: Context): Promise<User> {
    return getUserById( _id);
  }
 }

Whenever I try to fire the user resolver I get the following error

{
  "data": null,
  "errors": [
    {
      "message": "Expected value of type \"User\" but got: { _id: 5b530bec3b461c03cef31a53,\n  firstName: 'Danny',\n  lastName: 'Paul',\n  email: 'danny@paul.io',\n  password: null,\n  createdAt: 2018-07-21T10:33:26.763Z,\n  updatedAt: 2018-07-21T10:33:26.763Z,\n  __v: 0 }.",
      "locations": [
        {
          "line": 9,
          "column": 3
        }
      ],
      "path": [
        "user"
      ],
      "extensions": {
        "code": "INTERNAL_SERVER_ERROR",
        "exception": {
          "message": "Expected value of type \"User\" but got: { _id: 5b530bec3b461c03cef31a53,\n  firstName: 'Danny',\n  lastName: 'Paul',\n  email: 'danny@paul.io',\n  password: null,\n  createdAt: 2018-07-21T10:33:26.763Z,\n  updatedAt: 2018-07-21T10:33:26.763Z,\n  __v: 0 }.",
          "locations": [
            {
              "line": 9,
              "column": 3
            }
          ],
          "stacktrace": [
            "GraphQLError: Expected value of type \"User\" but got: { _id: 5b530bec3b461c03cef31a53,",
            "  firstName: 'Danny',",
            "  lastName: 'Paul',",
            "  email: 'danny@paul.io',",
            "  password: null,",
            "  createdAt: 2018-07-21T10:33:26.763Z,",
            "  updatedAt: 2018-07-21T10:33:26.763Z,",
            "  __v: 0 }.",
            "    at invalidReturnTypeError (/usr/src/app/node_modules/graphql/execution/execute.js:766:10)",
            "    at completeObjectValue (/usr/src/app/node_modules/graphql/execution/execute.js:758:13)",
            "    at completeValue (/usr/src/app/node_modules/graphql/execution/execute.js:660:12)",
            "    at completeValue (/usr/src/app/node_modules/graphql/execution/execute.js:629:21)",
            "    at /usr/src/app/node_modules/graphql/execution/execute.js:617:14",
            "    at process._tickCallback (internal/process/next_tick.js:68:7)"
          ]
        }
      }
    }
  ]
}

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.

MichalLytek commented 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.

danpaugo commented 6 years ago

@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

MichalLytek commented 6 years ago

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.

danpaugo commented 6 years ago

Oh ok cool. Thanks a lot for your time!

mabuonomo commented 6 years ago

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?

MichalLytek commented 6 years ago

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.

andreyvolfman commented 5 years ago

@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[]): {}'

MichalLytek commented 5 years ago

@danpaugo Try .toObject() first, before conversion (Object.setPrototypeOf): https://mongoosejs.com/docs/guide.html#toObject

andreyvolfman commented 5 years ago

@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

MichalLytek commented 5 years ago

@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.

andreyvolfman commented 5 years ago

@19majkel94 Thank you for answer!

ebrahim-2 commented 5 years ago

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.

Can you provide an example with getClassForDocument please...

MichalLytek commented 5 years ago

@EbrahimKreem https://github.com/szokodiakos/typegoose/blob/master/src/test/index.test.ts#L191-L215

anthowm commented 5 years ago

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;

    }
}
RyannGalea commented 5 years ago

I am having same issue as above

MichalLytek commented 5 years ago
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 🎉

RyannGalea commented 5 years ago

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 }."
MichalLytek commented 5 years ago

@RyannGalea _id: { _bsontype: means no .toObject() has been called by you

anthowm commented 5 years ago

@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!

RyannGalea commented 5 years ago

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
      }
anthowm commented 5 years ago

@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

RyannGalea commented 5 years ago

I'm guessing it's because the User class extends Typegoose, but I do not know how to remedy this.

anthowm commented 5 years ago

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.

RyannGalea commented 5 years ago

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.

RyannGalea commented 5 years ago

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

      }
RyannGalea commented 5 years ago

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

      }
MichalLytek commented 5 years ago

I hope this example will close the integration topic and discussions forever 😉

https://github.com/MichalLytek/type-graphql/tree/master/examples/typegoose

anthowm commented 5 years ago

@19majkel94 great work dude. thanks! @RyannGalea great work too!

RyannGalea commented 5 years ago

Thankyou @19majkel94 for taking the time to write that repo for us.

demouserforsale commented 5 years ago

@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
MichalLytek commented 5 years ago

@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.

oliviermattei commented 4 years ago

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...

MichalLytek commented 4 years ago

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.

alvaaz commented 4 years ago

@oliviermattei I've a problem with generate schema. This is issue Can you help me?

firebase007 commented 4 years ago

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

bryancolin commented 4 years ago

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);
RyannGalea commented 4 years ago

@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
bryancolin commented 4 years ago

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 Screen Shot 2020-05-28 at 9 07 32 PM

RyannGalea commented 4 years ago

@bryancolin Maybe share the code of what exactly you are doing. What is working, what isn't working

RyannGalea commented 4 years ago

@bryancolin Jump in the Typegoose Discord if you need more help, I might catch you in there https://discord.gg/Dvdzfp

3Pi commented 4 years ago

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 !

smolinari commented 4 years ago

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

MichalLytek commented 4 years ago

@smolinari Can you make a PR with that adjustment? I'm not using typegoose on my daily basis.

smolinari commented 4 years ago

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

M-benjamin commented 3 years ago

Thank you @MichalLytek