prismake / typegql

Create GraphQL schema with TypeScript classes.
https://prismake.github.io/typegql/
MIT License
423 stars 21 forks source link

How to create Field with OneToMany #10

Closed SergeEsmanovich closed 6 years ago

SergeEsmanovich commented 6 years ago

I try

 @OneToMany(type => Family, family => family.childhood)
    @Field({type: () => Family})
    family: Family[];

Error: @ObjectType Family.childhood: Validation of type failed. Resolved type for @Field must be GraphQLOutputType.

pie6k commented 6 years ago

Can you paste full definition of both sides of relation classes?

I've added better typeorm example that covers exactly this case.

Seems like Family class is not decorated with @ObjectType

eg.

@ObjectType() // <-- do you have Family class decorated?
class Family {
  // ...
}
SergeEsmanovich commented 6 years ago
import {Entity} from 'typeorm/decorator/entity/Entity';
import {BaseEntity, ManyToOne, PrimaryGeneratedColumn} from 'typeorm';
import {Profile} from './Profile';
import {FamilyRank} from './FamilyRank';
import {Childhood} from './Childhood';
import {FamilyStatus} from './FamilyStatus';
import {FamilyTragedy} from './FamilyTragedy';
import {Parents} from './Parents';
import {Field, ObjectType} from 'typegql';

@Entity()
@ObjectType()
export class Family extends BaseEntity{
    @PrimaryGeneratedColumn()
    @Field()
    id: number;

    @ManyToOne(type => Profile, profile => profile.family)
    @Field()
    profile: Profile;

    @ManyToOne(type => FamilyRank, familyRank => familyRank.family)
    @Field()
    familyRank: FamilyRank;

    @ManyToOne(type => Childhood, childhood => childhood.family)
    @Field()
    childhood: Childhood;

    @ManyToOne(type => FamilyStatus, familyStatus => familyStatus.family)
    @Field()
    familyStatus: FamilyStatus;

    @ManyToOne(type => FamilyTragedy, familyTragedy => familyTragedy.family)
    @Field()
    familyTragedy: FamilyTragedy;

    @ManyToOne(type => Parents, parents => parents.family)
    @Field()
    parents: Parents;
}

import {BaseEntity, Column, OneToMany, PrimaryGeneratedColumn} from 'typeorm';
import {Entity} from 'typeorm/decorator/entity/Entity';
import {Family} from './Family';
import {Field, ObjectType} from 'typegql';

@Entity()
@ObjectType()
export class Childhood extends BaseEntity {
    @PrimaryGeneratedColumn()
    @Field()
    id: number;

    @Column()
    @Field()
    name: string;

    @OneToMany(type => Family, family => family.childhood)
    @Field()
    family: Family[];
}
pie6k commented 6 years ago

Seems you have circular relationship (Childhood > Family > Childhood > Family) etc. In such case, you have to define type as function. (http://siawyoung.com/coding/javascript/circular-references-graphql-type-definitions.html)

Try to modify:

    @ManyToOne(type => Childhood, childhood => childhood.family)
    @Field()
    childhood: Childhood;

to

    @ManyToOne(type => Childhood, childhood => childhood.family)
    @Field({ type: () => Childhood })
    childhood: Childhood;

Also change

    @OneToMany(type => Family, family => family.childhood)
    @Field()
    family: Family[];

to

    @OneToMany(type => Family, family => family.childhood)
    @Field({ type: () => [Family] })
    family: Family[];

And let me know if that helps.

Note that if you use array types (eg. Family[]) you'd have to set type manually anyway as typescript is not able to guess type of array item.

SergeEsmanovich commented 6 years ago

it works but not quite. My query

{
  getFamilies{
    id
    childhood {
      id
      name
    } 
  }
}

Family:

@Entity()
@ObjectType()
export class Family extends BaseEntity {
    @PrimaryGeneratedColumn()
    @Field()
    id: number;

    @ManyToOne(type => Childhood, childhood => childhood.family)
    @Field({type: () => Childhood})
    childhood(): Childhood {
        return Childhood.findOneById(1);
    };
...
}

Childhood:

@Entity()
@ObjectType()
export class Childhood extends BaseEntity {
    @PrimaryGeneratedColumn()
    @Field()
    id: number;

    @Column()
    @Field()
    name: string;

    @OneToMany(type => Family, family => family.childhood)
    @Field({type: () => [Family]})
    family: Family[];
}

Schema:

@Schema()
class ApiSchema {
    @Query({type: [Family]})
    async getFamilies(): Promise<Family[]> {
        return await Family.find();
    }
}

Response:

{
  "data": {
    "getFamilies": [
      {
        "id": 1,
        "childhood": {
          "id": 1,
          "name": "Spent on the Street, with no adult supervision"
        }
      }
    ]
  }
}

I do not understand how to get here ID

@ManyToOne(type => Childhood, childhood => childhood.family)
@Field({type: () => Childhood})
childhood(): Childhood {
        return Childhood.findOneById(1);
};
MichalLytek commented 6 years ago

@korolariya

But sometimes you want to know what is the "profile id" of this user without loading the whole profile for this user. To do this you just need to add another property to your entity with @Column named exactly as the column created by your relation. http://typeorm.io/#/relations-faq/how-to-use-relation-id-without-joining-relation

You can take a look at this in my simple TypeORM example: https://github.com/19majkel94/type-graphql/blob/master/examples/typeorm-basic-usage/resolvers/rate-resolver.ts#L16

However the easier approach is to use lazy relations and let TypeORM fetch this automatically: https://github.com/19majkel94/type-graphql/blob/master/examples/typeorm-lazy-relations/entities/rate.ts

pie6k commented 6 years ago

@korolariya you don't need to define function that returns your Childhoods. Typeorm is handling it by itself.

So basically you could simply

@ManyToOne(type => Childhood, childhood => childhood.family)
@Field({type: () => Childhood})
childhood: Childhood;

If you'd need to add some custom logic you could do this like:

    @Field({type: () => Childhood})
    async childhood() {
        return Childhood.createQueryBuilder().relation("family").of(this).loadOne();
        // or some custom logic
    };

But it might not be needed propably in your case. In case you'd like to learn more about relational queries: http://typeorm.io/#/relational-query-builder

MichalLytek commented 6 years ago

Typeorm is handling it by itself.

@pie6k Only with lazy relations. Normally you need to overfetch them in main query or fetch manually in field resolver. Try that by yourself by creating more advanced TypeORM example than the basic one.

pie6k commented 6 years ago

Thanks @19majkel94 , I've made typeorm example more advanced and added relations example. You can check it out @korolariya .

Note that relation is marked as lazy so it can be fetched when graphql will need it.

image

SergeEsmanovich commented 6 years ago

@19majkel94, @pie6k It awesome, thanks. Everything works, I will continue to study.