How to return the entity with its relations after saving it? - express

I am building a storage application, with GraphQL as the backend, using Typegraphql and TypeORM.
The categories need to be added separately and then when adding a product, you choose from a dropdown one of the available categories. This in turn passes the categoryId to the product in a one-to-many/many-to-one relationship.
Here is my Category entity:
import {
Entity,
PrimaryColumn,
Column,
BaseEntity,
Generated,
OneToMany
} from 'typeorm';
import Product from './Product';
#ObjectType()
#Entity('categories')
export default class Category extends BaseEntity {
#Field()
#PrimaryColumn()
#Generated('uuid')
categoryId: string;
#Field()
#Column()
categoryName: string;
#OneToMany(() => Product, (product: Product) => product.category)
products: Product[];
}
and here is my Product entity
import {
Entity,
PrimaryColumn,
Column,
BaseEntity,
Generated,
ManyToOne,
JoinColumn
} from 'typeorm';
import Category from './Category';
#ObjectType()
#Entity('products')
export default class Product extends BaseEntity {
#Field()
#PrimaryColumn()
#Generated('uuid')
productID: string;
#Field()
#Column()
productName: string;
#Field(() => Category)
#ManyToOne(() => Category, (category: Category) => category.products, {
cascade: true,
lazy: true
})
#JoinColumn()
category: Category;
#Field()
#Column()
productQuantity: number;
#Field()
#Column({ type: 'decimal', precision: 2 })
productPrice: number;
#Field()
#Column({ type: 'decimal', precision: 2 })
productPriceRA: number;
#Field()
#Column({ type: 'decimal', precision: 2 })
productPriceKK: number;
#Field()
#Column('varchar', { length: 255 })
productSupplier: string;
#Field()
#Column('varchar', { length: 255 })
productOrderLink: string;
#Field()
#Column('longtext')
productImage: string;
}
For the save mutation, I've created an Input type as well:
export default class ProductInput implements Partial<Product> {
#Field()
productName: string;
#Field(() => String)
category: Category;
#Field()
productQuantity: number;
#Field()
productPrice: number;
#Field()
productPriceRA: number;
#Field()
productPriceKK: number;
#Field()
productSupplier: string;
#Field()
productOrderLink: string;
#Field()
productImage: string;
}
The relations work, as I am able to query the products, along with their category data with the following query:
{
getProducts {
productID
productName
category {
categoryId
categoryName
}
}
}
However, when saving a product it always returns
"message": "Cannot return null for non-nullable field Category.categoryName."
This is the Mutation's code in the Resolver:
#Mutation(() => Product, { description: 'Add new product' })
async addProduct(
#Arg('product') productInput: ProductInput
): Promise<Product | any> {
try {
const product = await Product.create(productInput).save();
console.log('product: ', product);
return product;
} catch (error) {
return error;
}
}
I've been trying different things, however nothing seems to work and I am wondering if it's even possible to directly return the entity with its relations. If it's not, the other option I can think of is to return true/false based on the result and re-query all of the data. But this seems very inefficient and I am actively trying to avoid going this route.
Any help will be much appreciated.

After some more research and I decided to go with the following approach:
try {
const { productID } = await Product.create(productInput).save();
return await Product.findOne(productID);
} catch (error) {
return error;
}
This allows me to directly return the product, based on the productID after it's saved in the database and properly returns the object with it's relationship.

GraphQL uses an notation to recognize data. You can see it as __typename object property. Of course, this must be turned on in the GraphQL server configuration. If you see it, it's already clear. You can reach the correct result without refetching the relation changes in the cached data on the client side with a trick like this.
For example, let's say we have updated the Product with category. In the data to return from the update mutation, it is sufficient to return only the id of the relation.
For this to work, category and product must be cached separately on the client beforehand.
for example:
mutation UpdateProduct($product: UpdateProductInput!) {
updateProduct(product: $product) {
id
title
category {
id
}
}
}
You can also write in writeFragment, which is a separate method, which is the most stingy, but it can make your job difficult in nested data.
export class ProductFragmentService {
constructor(private apollo: Apollo) {}
updateProduct(product: Product): void {
const client = this.apollo.client;
client.writeFragment({
id: `Product:${product.id}`,
fragment: gql`
fragment UpdateProductCategoryFragment on Product {
__typename
id
title
category {
id
}
}
`,
data: {
__typename: 'Product',
...product,
},
});
}
}
If you want all the fields belonging to category, you need to send them to resolver and return as a response from there. Otherwise, yes, it gives a warning that I could not find the name property.
The more profitable way of doing it is to send this data to the resolver with the input, as I wrote above, and return to the client as a response from the server.
If you still have to make another SQL request, it is necessary to call the same id after registration.
#Authorized()
#Mutation(() => Product, { description: 'Add new product' })
async addProduct(
#Arg('product') productInput: ProductInput
): Promise<Product> {
await this.productRepo.save(productInput);
return await this.productRepo.findOne({ where: { id: productInfo.id } });
}
that's all :)

Related

Many To Many Relation only accecepts Unique Values

Orders can have 0 to n Items in them. One Item can belongs to 0 to n Orders.
I have the Relationship set up the following way
#Entity()
export class Order {
#PrimaryGeneratedColumn()
id: number;
#ManyToOne(() => Customer, (customer) => customer.orders, {
eager: true,
})
customer: Customer;
#JoinTable()
#ManyToMany(() => Item, { eager: true })
items: Item[];
}
But I can only add Unique Items to my order. When I try to safe a item twice, it gets ignored?
This is the code for adding Items to a order
async addItemToOrder(orderId: number, itemId: number) {
const order = await this.findOne(orderId);
const item = await this.itemService.findOne(itemId);
if (!order.items) {
order.items = [];
order.items = [...order.items, item];
} else {
order.items = [...order.items, item];
}
order.totalPrice = this.calcTotalPrice(order.items);
await this.orderRepository.save(order);
return order;
}
This is the item
#Entity()
export class Item {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#Column()
price: number;
}
It is working as intended. In the underlying join table a itemId gets connected to a orderId. Typeorm only inserts a new entry if it cannot find a combination of itemId and orderId.
For your usecase it would make sense to define a own join table that includes a amount attribute. So your join table looks like this
itemId
orderId
amount
1
1
2
1
2
1
2
2
5
You can achive this using typeorm like this:
You create a new Entity that is the join entity between an item and an order and includes a attribute amount
#Entity()
export class OrderItem {
#Column('int')
amount: number;
#ManyToOne(() => Item, item => item.orders, { primary: true })
item: Item;
#ManyToOne(() => Order, order => order.items, { primary: true })
order: Order;
}
#Entity()
export class Order {
#PrimaryGeneratedColumn()
id: number;
#ManyToOne(() => Customer, (customer) => customer.orders, {
eager: true,
})
customer: Customer;
#OneToMany(() => OrderItem, { eager: true })
items: OrderItem[];
}
#Entity()
export class Item {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#Column()
price: number;
#OneToMany(() => OrderItem, { eager: true })
orders: OrderItem[];
}
The reason you do this is normalization. Relational databases rely on normalization to prevent inconsistent data.
You can read more about normalization here

How do I add a `relation` to my PostgresQL database using Typeorm?

For example
#Entity()
class Post {
#Column()
post_hash: string;
#Column()
title: string;
categorys: Array<Category> = [];
}
#Entity()
class Category {
#Column()
content: string;
#Column()
post_hash: number;
}
I want to query all the category content of the corresponding post through Typeorm.
I tried this method and failed.
this.createQueryBuilder('Post').leftJoinAndMapOne(
'Post.categorys',
Category,
'category',
'category.post_hash = post.post_hash'
)
This is my error message.
QueryFailedError: relation "post" does not exist
Entities
First of all, you must identify each row with a unique ID using #PrimaryColumn() or #PrimaryGeneratedColumn() decorators.
Each Post entity can have multiple categories and each Category entity is related to multiple posts.
This is called a many-to-many relation and must be correctly mapped using the #ManyToMany() decorator.
See this for more information.
#Entity()
class Post {
#PrimaryGeneratedColumn()
id: number;
#Column()
post_hash: string;
#Column()
title: string;
#ManyToMany(() => Category, category => category.posts)
#JoinTable()
categories: Category[];
}
#Entity()
class Category {
#PrimaryGeneratedColumn()
id: number;
#Column()
content: string;
#Column()
post_hash: number;
#ManyToMany(() => Post, post => post.categories)
posts: Post[];
}
Query
Obtain all Post(s) with the related categories.
Using find:
const posts: Post[] = await getRepository(Post)
.find({ relations: ['categories'] });
Using QueryBuilder:
const posts: Post[] = await getRepository(Post)
.createQueryBuilder('post')
.leftJoinAndSelect('post.categories', 'category')
.getMany();

How to filter the find method for a field on its relations?

The structure of my application is that I have:
Contacts
Groups
Lanes (belong to groups)
GroupContacts (join table to assign a contact to a group and assign a lane)
My entities are:
lane.entity.ts
import {
Entity,
Column,
PrimaryGeneratedColumn,
ManyToOne,
Unique,
OneToMany,
} from 'typeorm';
import { Group } from './group.entity';
import { GroupContact } from './groupcontact.entity';
#Entity('lanes')
#Unique('UQ_NAMES', ['group', 'name'])
export class Lane {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#Column()
sequence: number;
#Column({ nullable: false, default: 30 })
updateFrequencyDays: number;
#Column({
nullable: false,
default: () => 'CURRENT_TIMESTAMP',
type: 'timestamp',
})
createdAt: Date;
#ManyToOne(
type => Group,
group => group.lanes,
)
group: Group;
#OneToMany(
type => GroupContact,
groupcontact => groupcontact.lane,
)
groupContacts: GroupContact[];
}
groupcontact.entity.ts
import { Contact } from '../contacts/contact.entity';
import { Entity, Column, PrimaryColumn, ManyToOne, JoinColumn } from 'typeorm';
import { Group } from './group.entity';
import { Lane } from './lane.entity';
#Entity('groups_contacts')
export class GroupContact {
#PrimaryColumn()
groupId: number;
#PrimaryColumn()
contactId: number;
#Column()
laneId: number;
#Column({
nullable: false,
default: () => 'CURRENT_TIMESTAMP',
type: 'timestamp',
})
createdAt: Date;
#ManyToOne(
type => Group,
group => group.contactConnection,
)
#JoinColumn({ name: 'groupId' })
group: Group;
#ManyToOne(
type => Contact,
contact => contact.groupConnection,
)
#JoinColumn({ name: 'contactId' })
contact: Contact;
#ManyToOne(
type => Lane,
lane => lane.groupContacts,
)
#JoinColumn({ name: 'laneId' })
lane: Lane;
}
contact.entity.ts
import { Group } from 'src/groups/group.entity';
import { GroupContact } from '../groups/groupcontact.entity';
import {
Entity,
Column,
PrimaryGeneratedColumn,
JoinTable,
ManyToMany,
OneToMany,
} from 'typeorm';
#Entity('contacts')
export class Contact {
#PrimaryGeneratedColumn()
public id: number;
#Column()
public firstName: string;
#Column()
public lastName: string;
#Column({
nullable: false,
default: () => 'CURRENT_TIMESTAMP',
type: 'timestamp',
})
public createdAt: Date;
#OneToMany(
type => GroupContact,
gc => gc.contact,
)
groupConnection: GroupContact[];
}
What I am trying to do is that I am trying to return all contacts that belong to a particular group. But when returning those contacts, I'd also like to return the groupConnection object.
Here is the code I am using:
async getContacts(groupId) {
const group = await this.findOneByIdOrThrow(groupId);
const contacts = await this.contactRepository.find({
//where: { groupConnection: { groupId: groupId } },
relations: ['groupConnection'],
});
return contacts;
}
So far, am I able to return what I wanted, which is a list of contacts together with their relations. But I have not found a way to filter those results to retrieve only the ones that belong to a particular groupId.
Where clause commented.
How do I filter for that?
I was able to fix it by using QueryBuilder
const contacts = await this.contactRepository
.createQueryBuilder('contacts')
.innerJoinAndSelect('contacts.groupConnection', 'groupConnection')
.where('groupConnection.groupId = :groupId', { groupId })
.getMany();

GraphQL & Sequelize: Users and followers/following

I'm attempting to set up my User GraphQL model to have followers and following attributes to query on. However I'm having trouble setting up the relationship in Sequelize. I'm trying to use a Follower model as a Join Table and setup a BelongsToMany association, but haven't been able to get it working. Can anyone suggest what to do or point out what I'm doing wrong?
I've come up with a temporary solution by manually querying, which you can see in my User.model.ts, but I believe there is a better way to do it using proper configuration.
I'm using typescript wrappers around GraphQL and Sequelize, TypeGraphQL and sequelize-typescript respectively, as well as PostgreSQL.
User.model.ts
// VENDOR
import { ObjectType, Field, ID } from 'type-graphql';
import { Model, Table, Column, PrimaryKey, Unique, IsUUID, HasMany, DefaultScope, AllowNull, DataType, BelongsToMany } from 'sequelize-typescript';
// APP
import Post from '../post/post.types';
import Follower from '../follower/follower.types';
/** User model for GraphQL & Database */
#Table({ timestamps: false, tableName: 'users' }) // tell sequelize to treat class as table model
#DefaultScope(() => ({ include: [{ model: Post.scope(), as: 'posts' }] })) // tell sequelize to include posts in its default queries
#ObjectType() // tell GraphQL to treat class as GraphQL model
export default class User extends Model<User>{
#PrimaryKey
#Unique
#AllowNull(false)
#IsUUID(4)
#Column(DataType.UUID)
#Field(() => ID)
id: string;
#Unique
#AllowNull(false)
#Column
#Field()
ci_username: string;
#AllowNull(false)
#Column
#Field()
username: string;
#AllowNull(false)
#Column
#Field()
first_name: string;
#Column
#Field()
last_name: string;
#Column
#Field({ nullable: true })
profile_picture?: string;
// #BelongsToMany(() => User, { otherKey: 'user_id', as: 'followers', through: () => Follower })
// #Field(() => [User])
// followers: User[];
// MY TEMPORARY SOLUTION USING MANUAL QUERYING
#Field(() => [User])
get followers(): Promise<User[]> {
return Follower.findAll({ where: { user_id: this.id } })
.then(records => records.map(record => record.follower_id))
.then((follower_ids: string[]) => {
return User.findAll({ where: { id: follower_ids }});
})
}
// DOES NOT WORK, BUT I BELIEVE COULD POTENTIALLY LEAD TO BETTER SOLUTION
#BelongsToMany(() => User, { otherKey: 'follower_id', as: 'following', through: () => Follower })
#Field(() => [User])
following: User[];
#HasMany(() => Post)
#Field(() => [Post])
posts: Post[];
}
Follower.model.ts
// VENDOR
import { Model, Table, Column, PrimaryKey, Unique, IsUUID, AllowNull, DataType, Index, ForeignKey, AutoIncrement } from 'sequelize-typescript';
// APP
import User from '../user/user.types';
/** Follower model for Database */
#Table({ timestamps: false, tableName: 'followers' }) // tell sequelize to treat class as table model
export default class Follower extends Model<Follower>{
#PrimaryKey
#AutoIncrement
#Unique
#AllowNull(false)
#Column
id: number;
#AllowNull(false)
#IsUUID(4)
#Index
#ForeignKey(() => User)
#Column(DataType.UUID)
user_id: string;
#AllowNull(false)
#IsUUID(4)
#Index
#ForeignKey(() => User)
#Column(DataType.UUID)
follower_id: string;
}
GraphQL Query
{
users: allUsers {
id
username
first_name
last_name
following {
username
id
}
}
}
GraphQL Response / Error
{
"errors": [
{
"message": "Cannot return null for non-nullable field User.following.",
"locations": [
{
"line": 7,
"column": 5
}
],
"path": [
"users",
0,
"following"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"stacktrace": [
"Error: Cannot return null for non-nullable field User.following.",
" at completeValue (/Users/jsainz237/Projects/trueview/trueview-api/node_modules/graphql/execution/execute.js:560:13)",
" at /Users/jsainz237/Projects/trueview/trueview-api/node_modules/graphql/execution/execute.js:492:16"
]
}
}
}
],
"data": null
}
Any help is appreciated.
You need to write a #FieldResolver manually that will resolve the relation and return proper data.
Another solution is to rely on ORM capabilities and lazy relations - when the returned base entity contains a promise as a field, so when .then() is called, it automatically fetches the relation for the database.

TypeORM getRepository.find() does not include Foreign Key Fields

I am trying to fetch all the columns included on my entity, but I only able to fetch the columns that does not have any relationship from the other entity.
I use this block of codes to fetch the all the rows to this repository.
private translationTextRepository = getRepository(TranslationText);
async all(request: Request, response: Response, next: NextFunction) {
return this.translationTextRepository.find();
}
And here's the entity for this repository.
#Entity('TranslationText')
export class TranslationText {
#PrimaryGeneratedColumn()
ID: number;
#Column()
CreatedBy: string;
#Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
CreatedDate: Date;
#Column()
Status: boolean;
#Column({ nullable: true, default: null })
ModifiedBy: string;
#Column({ type: 'timestamp', nullable: true, default: null })
ModifiedDate: Date;
#Column()
Text: string;
#ManyToOne((type) => Locale, (locale) => locale.ID)
#JoinColumn({ name: 'LocaleID' })
LocaleID: Locale;
#ManyToOne((type) => TranslationTitle, (translationTitle) => translationTitle.ID)
#JoinColumn({ name: 'TranslationTitleID' })
TranslationTitleID: TranslationTitle;
}
But I was only able to fetch all the columns except the LocaleID and the TranslationTitleID.
How can I achieve this?
Check this document:
https://typeorm.io/#/relations-faq/how-to-use-relation-id-without-joining-relation
solution:
define new column:
#column()
LocaleID: number
rename old one to : Locale
But typeOrm cannot sync your table due to foreign key problem.
use eager option in #ManyToOne({eager: true})
The search result will contain relation Locale object, you can take id from it.
Can you try to specify the relations like that:
async all(request: Request, response: Response, next: NextFunction) {
return this.translationTextRepository.find({
relations:["LocaleID","TranslationTitleID"]
});
}
Because you have to make explicit that you want your relations on the query.