I'm using type-graphql in conjunction with typeorm, apollo-server-express and postgreSQL. I have a User and a Customer entity in a 1:n relationship, meaning one user can have multiple customers.
I can create users and customers just fine, but when attempting to retrieve the user associated to a customer using Apollo Server playground, I get an error message stating "Cannot return null for non-nullable field Customer.user."
When I check the database, the associated user id on the customer table is definitely not null (see attached image).
query {
customers {
customerId
customerName
user {
userId
}
}
}
Does anyone know what I'm doing wrong?
User.ts
import { Field, ID, ObjectType } from "type-graphql";
import { BaseEntity, Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
import { Customer } from "./Customer";
#ObjectType()
#Entity("users")
export class User extends BaseEntity {
#Field(() => ID)
#PrimaryGeneratedColumn("uuid")
userId: string;
#Field()
#Column({ unique: true })
email: string;
#Column({ nullable: false })
password: string;
#Field(() => Customer)
#OneToMany(() => Customer, customer => customer.user)
customers: Customer[]
}
Customer.ts
import { Field, ID, ObjectType } from "type-graphql";
import { BaseEntity, Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
import { User } from "./User";
#ObjectType()
#Entity("customers")
export class Customer extends BaseEntity {
#Field(() => ID)
#PrimaryGeneratedColumn("uuid")
customerId: string;
#Field()
#Column()
customerName: string;
#Field(() => User)
#ManyToOne(() => User, user => user.customers)
user: User;
}
CustomerResolver.ts
export class CustomerResolver {
#Query(() => [Customer])
async customers():Promise<Customer[]> {
try {
return await Customer.find();
} catch (error) {
console.log(error);
return error;
}
}
....
Setup / Version
Node: v14.17.0
"apollo-server-express": "^2.24.0",
"type-graphql": "^1.1.1",
"typeorm": "0.2.32"
postgreSQL: 13.2
In your resolver change the find operation like below:
return Customer.find({
relations: ["user"]
});
You should write a #FieldResolver which will fetch customers based on root user data.
https://typegraphql.com/docs/resolvers.html#field-resolvers
Related
I dont' know where to start, I tried follow a answer by import Geometry from Geojson.
User.ts file
import { Geometry } from "geojson";
import { Field, ObjectType } from "type-graphql";
import {
BaseEntity,
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
} from "typeorm";
#ObjectType()
#Entity()
export class User extends BaseEntity {
#Field()
#PrimaryGeneratedColumn()
id!: number;
#Column({ unique: true })
userId!: string;
#Field(type => Geometry)
#Column({ nullable: true })
location: Geometry;
#Field()
#CreateDateColumn()
createdAt: Date;
}
but I got a error with PosgreSQL
DataTypeNotSupportedError: Data type "Object" in "User.location" is not supported by "postgres" database.
and a error with Graphql
'Geometry' only refers to a type, but is being used as a value here.
I have an OneToMany relation from User to Message.
When I insert register a user with a message, it adds the user to the User table, and the message to the Message table with a userId pointing to the user's id.
This is done automatically using the following setup.
User entity:
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#Column()
email: string;
#JoinTable()
#OneToMany((type) => Message, (message) => message.user, {
cascade: true,
})
messages: Message[];
}
Message entity:
#Entity()
export class Message {
#PrimaryGeneratedColumn()
id: number;
#Column()
text: string;
#ManyToOne((type) => User, (user) => user.messages, { eager: true })
user: User[];
}
If I want to find all messages from a user via userId with:
const existing_msgs = await this.messageRepository.find({
where: { userId: user.id },
});
It tells me that it cannot find the userId column, which is understandable as I did not specifically include userId to the Message entity.
But how would we query it in this case?
As mentionned in This post, you have to add the decorator #JoinColumn after your manytoone. This will join the column and you will be able to perform the query you want with :
const existing_msgs = await this.messageRepository.find({
where: { user: { id: user.id }},
});
Note that this will work only on primary column like id.
And your message table will be :
#Entity()
export class Message {
#PrimaryGeneratedColumn()
id: number;
#Column()
text: string;
#ManyToOne((type) => User, (user) => user.messages, { eager: true })
#JoinColumn() // <-- Add this
user: User[];
}
I was able to do it with the following querybuilder.
const msg_arr = await this.userRepository
.createQueryBuilder('user')
.leftJoinAndSelect('user.messages', 'messages')
.where('user.id = :userId', { userId: user.id })
.andWhere('messages.text LIKE :text', { text: message })
.select('messages.text')
.execute();
I am coding a CRUD API built in TypeScript and TypeGoose.
I get an error saying,
CannotDetermineGraphQLTypeError: Cannot determine GraphQL output type for '_id' of 'User' class. Is the value, that is used as its TS type or explicit type, decorated with a proper decorator or is it a proper output value?
I have a User entity.
import { Field, ObjectType } from 'type-graphql';
import { ObjectId } from 'mongodb';
import { prop as Property, getModelForClass } from '#typegoose/typegoose';
#ObjectType()
export class User {
#Field()
readonly _id: ObjectId;
#Field()
#Property({ required: true })
email: string;
#Field({ nullable: true })
#Property()
nickname?: string;
#Property({ required: true })
password: string;
constructor(email: string, password: string) {
this.email = email;
this.password = password;
}
}
export const UserModel = getModelForClass(User);
And this is how my query resolver looks like.
#Query(() => [User])
async users() {
const users = await UserModel.find();
console.log(users);
return users;
}
How can I solve this? It seems to be like TypeGraphQL doesn't understand what the MongoDB ID is?
Im not sure about this, but maybe ObjectId.toString() help you.
MongoDB doc about ObjectId.toString()
Below I have the Equipt entity it has three columns createdById, tribeId, userId.
I am trying to save a new row using the id's of each entity, and not the entities themselves:
This doesn't work:
let e = connection.getRepository(Equipt);
const check = await e.save({
userId: 1,
tribeId: 1,
createdById: 1,
})
This works:
let e = connection.getRepository(Equipt);
const check = await e.save({
user: user,
tribe: tribe,
createdBy: adminUser,
})
Entity:
import {ManyToOne, RelationId, JoinColumn, Entity} from "typeorm";
import {User} from './User';
import { Base } from "../base";
import { Tribe } from "./Tribe";
#Entity('Equipts')
export class Equipt extends Base {
#ManyToOne(type => User, { nullable: false })
#JoinColumn()
createdBy: User;
#RelationId((Equipt: Equipt) => Equipt.createdBy)
createdById: number;
#ManyToOne(type => Tribe, { nullable: false })
#JoinColumn()
Tribe: Tribe;
#RelationId((Equipt: Equipt) => Equipt.Tribe)
TribeId: number;
#ManyToOne(type => User, { nullable: false })
#JoinColumn()
user: User;
#RelationId((Equipt: Equipt) => Equipt.user)
userId: number;
}
Is there any way to insert using id's without having the pass the entire entity?
I just hit a pretty interesting problem when using TypeORM and joining tables that I've set up for a Postgres database. I figured it out but thought I'd post the info here for anyone else that had a similar problem.
I have 3 tables set up on my database: user, organisation, user_organisation.
The idea for this is that a user can belong to many organisations and the table called user_organisation maps users to these organisations. So my entities look like this,
user.entity.ts
import { TimestampedEntity } from '#shared/entities/timestamped.entity';
import { Organisation } from '#user/entities/organisation.entity';
import { UserOrganisation } from '#user/entities/user-organisation.entity';
import { Column, Entity, Index, JoinTable, ManyToMany, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
#Entity()
#Index(['email', 'password'])
export class User extends TimestampedEntity {
#PrimaryGeneratedColumn()
userId: number;
#Column({
length: 64
})
username: string;
#Column({
length: 500
})
email: string;
#Column({
length: 255
})
password: string;
#Column({
type: 'json',
})
details: any;
#Column({
nullable: true
})
refreshToken: string;
#OneToMany(type => UserOrganisation, userOrganisation => userOrganisation.user)
#JoinTable()
userOrganisations: UserOrganisation[];
}
user-organisation.entity.ts
import { Organisation } from '#user/entities/organisation.entity';
import { User } from '#user/entities/user.entity';
import { Column, Entity, JoinColumn, ManyToOne, OneToOne, PrimaryColumn, PrimaryGeneratedColumn } from 'typeorm';
#Entity()
export class UserOrganisation {
#ManyToOne(type => User, user => user.userOrganisations, { primary: true })
user: User;
#ManyToOne(type => Organisation, organisation => organisation.userOrganisations, { primary: true })
organisation: Organisation;
}
organisation.entity.ts
import { TimestampedEntity } from '#shared/entities/timestamped.entity';
import { UserOrganisation } from '#user/entities/user-organisation.entity';
import { User } from '#user/entities/user.entity';
import { Column, Entity, JoinColumn, JoinTable, ManyToMany, ManyToOne, OneToMany, OneToOne, PrimaryGeneratedColumn } from 'typeorm';
#Entity()
export class Organisation extends TimestampedEntity {
#PrimaryGeneratedColumn()
orgId: number;
#Column({
length: 255
})
name: string;
#Column({
type: 'json'
})
details: any;
#OneToMany(type => UserOrganisation, userOrganisation => userOrganisation.organisation)
userOrganisations: UserOrganisation[];
}
I was then trying to run the following query,
this.userRepository.createQueryBuilder('user')
.where('user.email = :email', { email })
.innerJoin(UserOrganisation, 'userOrganisation', 'user.userId = userOrganisation.userUserId')
.getOne();
And this is the error message I got,
ERROR: missing FROM-clause entry for table "userorganisation" at character 401
The final query printed out like this,
SELECT "user"."createdAt" AS "user_createdAt"
, "user"."updatedAt" AS "user_updatedAt"
, "user"."userId" AS "user_userId"
, "user"."username" AS "user_username"
, "user"."email" AS "user_email"
, "user"."password" AS "user_password"
, "user"."details" AS "user_details"
, "user"."refreshToken" AS "user_refreshToken"
FROM "user" "user"
INNER JOIN "user_organisation" "userOrganisation"
ON "user"."userId" = userOrganisation.userUserId
WHERE "user"."email" = $1
The way I fixed it is described below.
What I noticed in the query,
SELECT "user"."createdAt" AS "user_createdAt", "user"."updatedAt" AS "user_updatedAt", "user"."userId" AS "user_userId", "user"."username" AS "user_username", "user"."email" AS "user_email", "user"."password" AS "user_password", "user"."details" AS "user_details", "user"."refreshToken" AS "user_refreshToken" FROM "user" "user" INNER JOIN "user_organisation" "userOrganisation" ON "user"."userId" = userOrganisation.userUserId WHERE "user"."email" = $1
Was that there was a difference between the user table and the userOrganisation table in the join criteria,
"user"."userId" = userOrganisation.userUserId
The user table was automatically wrapped in quotation marks but userOrganisation was not... So I changed my code to the following,
this.userRepository.createQueryBuilder('user')
.where('user.email = :email', { email })
.innerJoin(UserOrganisation, 'userOrganisation', '"user"."userId" = "userOrganisation"."userUserId"')
.getOne();
If you look above, I've added the quotation marks in the join criteria. It's all working now :)
Hope this helps!
Well done. However when using joins in typeorm you have to write the conditions as they are defined then setting objects.
this.userRepository.createQueryBuilder('user')
.where('user.email = :email', { email })
.innerJoin(UserOrganisation, 'userOrganisation', 'user.userId=userOrganisation.user.userId')
.getOne();
With this you do not need to add quotation marks.
The generated sql is not showing the quotes because it does not know how to populate the conditions.
I hope this helps.