UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'type' of undefined

936 views Asked by At

I'm using type-graphql with apollo-server and I'm trying to handle errors using union types for example I want to return an arrary of GQLError(custom type) when something goes wrong in Query/Mutation. My code for Resolver, Entity and custom Union Types:

user/entity.ts:

import { 
  BaseEntity,
  Entity,
  PrimaryColumn,
  Column,
  CreateDateColumn,
  UpdateDateColumn,
  BeforeInsert
} from "typeorm";
import { ObjectType, Field, ID } from "type-graphql";
import { v4 as uuid } from "uuid";
import * as bcrypt from "bcrypt";

@Entity("users")    
@ObjectType()
export class User extends BaseEntity {
  @PrimaryColumn("uuid")
  @Field(() => ID)
  id: string;
  
  @Column("varchar", { length: 255, unique: true })
  @Field()
  username: string;
    
  @Column("varchar", { length: 255 })
  @Field()
  password: string;    
  
  @Column("varchar", { length: 255, nullable: true })
  @Field()
  email?: string;

  @CreateDateColumn()
  created: Date;

  @UpdateDateColumn()
  updated: Date;

  @BeforeInsert()
  async setup(): Promise<void> {
    this.id = uuid();
    this.password = await bcrypt.hash(this.password, bcrypt.genSaltSync(12));
  }
}

user/types.ts

import { createUnionType } from "type-graphql";             
                                                            
import { GQLErrorResponse } from "../shared/index.entity";  
import { User } from "./entity";                            
                                                            
export const RegistrationResponse = createUnionType({       
  name: "UserRegistrationResponse",                         
  types: () => [User, GQLErrorResponse] as const            
});                                                         
                                                            
export const LoginResponse = createUnionType({              
  name: "UserLoginResponse",                                
  types: () => [User, GQLErrorResponse] as const            
});                                                         
                                                            
export const UserQueryResponse = createUnionType({          
  name: "UserQueryResponse",                                
  types: () => [User, GQLErrorResponse] as const,           
  resolveType: (value: User | GQLErrorResponse) => {        
    if ("id" in value) {                                    
      return User;                                          
    } else {                                                
      return GQLErrorResponse;                              
    }                                                       
  }                                                         
});                                                         
                                                            
export const UsersQueryResponse = createUnionType({         
  name: "UsersQueryResponse",                               
  types: () => [User, GQLErrorResponse] as const            
});                                                         

user/resolver.ts

import { Resolver, Arg, Query, Mutation } from "type-graphql";
import * as bcrypt from "bcrypt";
import * as _ from "lodash";

import { User } from "./entity";
import { UserRegistrationInput, UserLoginInput } from "./inputs";
import { UserQueryResponse, UsersQueryResponse } from "./types";

@Resolver(User)
export class UserResolver {
  @Query(() => [User])
  async users(): Promise<User[]> {
    return User.find({});
  }

  @Query(() => UserQueryResponse)
  async user(
    @Arg("id", { nullable: true }) id?: string
  ): Promise<typeof UserQueryResponse> {
    const user: User | undefined = await User.findOne(id);
    if (_.isEmpty(user)) {
      return {
        errors: [
          {
            path: "id",
            message: "User not found"
          }
        ]
      };
    }
    return user as User;
  }

  @Mutation(() => User)
  async register(@Arg("input") input: UserRegistrationInput): Promise<User> {
    const user: User = await User.create(input).save();

    return user;
  }

  @Mutation(() => User)
  async login(@Arg("input") input: UserLoginInput): Promise<User> {
    const user: User | undefined = await User.findOne({
      where: { username: input.username }
    });
    const valid: boolean = await bcrypt.compare(input.password, user.password);
    if (!valid) {
      throw new Error("Invalid username/password");
    }
                                                                                                          
    return user;                                                                                          
  }                                                                                                       
}                                                                                                         

However, When I run my code I get the following error:

(node:325229) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'type' of undefined
1

There are 1 answers

0
Florian Bachmann On

I found the problem was due to circular dependencies, or rather: wrong order of imports.

Old answer:

It's just a guess, because for me that was the issue: have you tried to change the type of the id fields from ID to Int?

Anyway, in my case, I found the origin of the problem when I changed the code of type-graphql in the line given in the error:

node:28896) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'type' of undefined at interfaceClasses.map.interfaceClass (/workspaces/assistant-private/node_modules/type-graphql/dist/schema/schema-generator.js:164:149)

So, I went in to that schema-generator.js and found this:

types: () => unionClassTypes.map(objectType => this.objectTypesInfo.find(type => type.target === objectType).type),

It turned out, that objectType was already undefined, so I changed it to this:

types: () => unionClassTypes.filter(a => a).map(objectType => this.objectTypesInfo.find(type => type.target === objectType).type),

After that, I got the following error instead of TypeError: Cannot read property 'type' of undefined:

GraphQLError: Interface field IBaseEntity.id expects type Int! but BaseAction.id is type Float!.