TypeORM relations and express - javascript

I'm making Many to One, One to many relations with TypeORM and MySQL, and Express. There are two tables cold post and user.
One user has many posts. and each post has one user.
I would like to use 'userUuid' column as a foreign key to join these two tables.
Can anybody tell me how to fix it?
Thank you
// user
import {
Entity,
PrimaryGeneratedColumn,
Column,
BaseEntity,
BeforeInsert,
Generated,
OneToMany,
} from 'typeorm'
import { v4 as uuid } from 'uuid'
import { Post } from './Post'
#Entity()
export class User extends BaseEntity {
#PrimaryGeneratedColumn()
id!: number
#Column()
userId!: string
#Column()
#Generated('uuid')
userUuid!: string
#Column({ nullable: true })
displayName!: string
#Column({ nullable: true })
email!: string
#OneToMany(() => Post, (post: Post) => post.user)
posts!: Post[]
#BeforeInsert()
createUuid() {
this.userUuid = uuid()
}
toJSON() {
return { ...this, id: undefined }
}
}
// post
import {
Entity,
PrimaryGeneratedColumn,
CreateDateColumn,
UpdateDateColumn,
Column,
BaseEntity,
Generated,
BeforeInsert,
ManyToOne,
JoinColumn,
} from 'typeorm'
import { v4 as uuid } from 'uuid'
import { User } from './User'
#Entity()
export class Post extends BaseEntity {
#PrimaryGeneratedColumn()
id!: number
#Column()
#Generated('uuid')
postUuid!: string
#Column({ nullable: true })
userUuid!: string
#CreateDateColumn({ nullable: true })
createdAt!: Date
#UpdateDateColumn({ nullable: true })
updatedAt!: Date
#Column({ nullable: true })
content!: string
#BeforeInsert()
createUuid() {
this.postUuid = uuid()
}
#ManyToOne(() => User, (user: User) => user.posts)
user!: User
#JoinColumn([{ name: 'userUuid' }, { referencedColumnName: 'userUuid' }])
toJSON() {
return { ...this, id: undefined }
}
}
//express
router
.route('/')
.get((req: Request, res: Response) => {
User.find({ relations: ['posts'] })
.then((data) => {
res.send(data)
})
.catch((err) => res.send(err))
})
//express
router
.route('/')
.get((req: Request, res: Response) => {
Post.find({ relations: ['user'] })
.then((data) => {
res.send(data)
})
.catch((err) => res.send(err))
})

Seems like you need to use JoinColumn decorator.
class Post /*...*/ {
/*...*/
#ManyToOne(() => User)
#JoinColumn({ referencedColumnName: "userUuid" })
userUuid!: string;
/*...*/
}
From TypeORM docs
Join columns are always a reference to some other columns (using a foreign key). By default your relation always refers to the primary column of the related entity. If you want to create relation with other columns of the related entity - you can specify them in #JoinColumn as well:
#ManyToOne(type => Category)
#JoinColumn({ referencedColumnName: "name" })
category: Category;
The relation now refers to name of the Category entity, instead of id. Column name for that relation will become categoryName.

Related

Get the child type from the parent in typeorm

I have a typeorm entity that uses single table inheritance:
#Entity()
#TableInheritance({ column: { type: "varchar", name: "type" } })
#ObjectType({ isAbstract: false })
export class OrganisationEntity extends BaseEntity {
#Field()
#Column()
name: string;
#Field(() => [UserEntity])
#OneToMany(() => UserEntity, (user) => user.organisation)
users: UserEntity[];
}
and some child entities:
#ChildEntity()
#ObjectType()
class MedicalOrganisation {
}
#ChildEntity()
#ObjectType()
class SoftwareOrganisation {
}
#ChildEntity()
#ObjectType()
class MedicalOrganisation {
}
I'm wondering how I can get the type and the child properties from the parent organisation so that I can do something like:
const organisation = await OrganisationEntity.findOne()
if(organisation.type === "medicalOrganisation"){
...
}
But it seems I'm not allowed to access the type property through the parent. Does anyone know how this can be done?
I'd prefer not to use instanceof because it requires the child entities and is causing circular dependencies.
You've got two options. Either use .createQueryBuilder and get the item using getRawOne and the returned object would contain a field named OrganisationEntity_type which can be used to do the checks. It's value would either be 'MedicalOrganisation', or 'SoftwareOrganisation' and so on.
const orgRepository: Repository<OrganisationEntity> = getRepository(OrganisationEntity);
let organisation = await orgRepository.createQueryBuilder().getRawOne();
// organisation = {
// OrganisationEntity_name: 'name',
// OrganisationEntity_users: [
// {},
// {}
// ],
// OrganisationEntity_type: 'MedicalOrganisation'
// }
Or, you could add the field type in the OrganisationEntity itself like this:
#Entity()
#TableInheritance({ column: { type: "varchar", name: "type" } })
#ObjectType({ isAbstract: false })
export class OrganisationEntity extends BaseEntity {
#Field()
#Column()
name: string;
#Field(() => [UserEntity])
#OneToMany(() => UserEntity, (user) => user.organisation)
users: UserEntity[];
#Column
type: string;
}
and do a straightforward:
let organisation = await OrganisationEntity.findOne();
// organisation =
// MedicalOrganisation {
// name: 'name',
// users: [{}, {}],
// type: 'MedicalOrganisation'
// }
and you get the type field in the object itself.

How insert data in manyToOne relationship ? NestJs

I'm new with ORM system and I'm trying to insert a data "Mark" binded with an object "pseudo".
This my entities,
Pseudo:
#Entity()
export class PseudoEntity{
#PrimaryGeneratedColumn()
id: number;
#Column({unique: true})
pseudo: string;
#Column({ default: 1 })
application_version: number;
#Column({ default: -1 })
score_on_last_calculation: number;
#Column({default: 1})
nb_request: number;
#Column({default: -1})
nb_mark_on_last_calculation: number;
#OneToMany(type => Mark, marks => marks)
marks: Mark[];
}
And Mark:
#Entity()
export class MarkEntity {
#PrimaryGeneratedColumn()
int: number;
#Column({ type: 'datetime'})
submission_date: Date;
#Column()
mark: number;
#ManyToOne(
type => Pseudo,
pseudo => pseudo,
)
pseudo: Pseudo;
}
This is my service to insert the data:
#Injectable()
export class MarkService {
constructor(private pseudoService: PseudoService) {}
async postMark(pseudo: string, mark: number) {
await this.pseudoService.findPseudo(pseudo).then(
p =>
getConnection()
.createQueryBuilder()
.insert()
.into(Mark)
.values([
{
mark: mark,
pseudo: p,
},
]),
);
}
}
I succeed to insert a Pseudo but not a mark :/
Could you help me please ?
Thanks in advance
First you should make a few slight changes to your entities:
#OneToMany(type => Mark, marks => marks.pseudo)
marks: Mark[];
#ManyToOne(
type => Pseudo,
pseudo => pseudo.marks,
)
pseudo: Pseudo;
Having made the changes above, use the injected pseudoRepository for querying and updating the desired Pseudo: (more about Registering and Injecting entities)
#Injectable()
export class MarkService {
constructor(#InjectRepository(Pseudo) private readonly pseudoRepository: Repository<Pseudo>) { }
async postMark(mark: number, pseudo: Pseudo): Promise<void> {
let _mark = new Mark();
mark.mark = mark;
mark.pseudo = await pseudoRepository.findOne({ pseudo });
await this.pseudoRepository.save(_mark)
}
}
I didn't try it but I suppose this should work:
import { Injectable } from '#nestjs/common';
import { PseudoEntity as Pseudo } from '../entity/pseudo.entity';
import { MarkEntity as Mark } from '../entity/mark.entity';
import { getConnection } from 'typeorm';
#Injectable()
export class PseudoService {
async postMark(markInput: number, pseudoInput: string) {
let mark = new Mark();
mark.mark = markInput;
mark.submission_date = Date.now();
mark.pseudo = await await repository.findOne({ pseudo: pseudoInput});
await getConnection().manager
.save(mark)
.then(mark => {
console.log("Mark has been saved. Mark id is ", mark.id);
});
}
//....
}
You should probably add a check in case no pseudo is found within the database for this pseudoInput.

TypeORM Throwing Duplication Error on Bulk saving instead of ignore or update existing value

As Documented on TypeOrm FrameWork Repository.save should save/insert new values and ignore/update the existing once,
But now I'm facing a problem that it's thrown a duplication error on existing value and stoping the whole inserting! ( I have a unique column called key )
My entity:
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, JoinColumn, PrimaryColumn } from 'typeorm';
import { Source } from '../sources/source.entity';
#Entity({ name: 'articles' })
export class Article {
#PrimaryGeneratedColumn()
id: number;
#Column()
title: string;
#Column({
nullable: true
})
image: string | null;
#Column({
type: "text",
})
url: string;
#Column({
type: "varchar",
unique: true
})
key: string;
#Column({
type: 'datetime',
nullable: true
})
date: Date | null;
#ManyToOne(type => Source, source => source.articles, {eager: true})
#JoinColumn({name: 'source'})
source: Source;
#Column({
type: `text`,
nullable: true
})
description: string | null
}
My Service:
constructor(
#InjectRepository(Article) private readonly articleRepository: Repository<Article>,
private readonly articlesScraper: BlogScraperService
) {
}
async clonningFromScraper() {
let articles = await this.articlesScraper.articles('1');
articles = articles.map(article => ({ ...article, key: decodeURIComponent(article.url).substring(0, 255) }));
return this.articleRepository
.save(articles);
}
I have ended up solving this by RAW SQL query using the following
return this.articleRepository.query(
"INSERT IGNORE INTO articles ( title, date, url, image, source, description, _key ) VALUES ?", [querableArticles]);

How to Make Relation in LoopBack v4 With Include Key

https://loopback.io/doc/en/lb4/HasMany-relation.html
I followed this steps and then tried to get data with include but I get 500.
500 Error: Invalid "filter.include" entries: {"relation":"ranks"}
What I want is to get games object with its related ranks.
Rank Model
import { Entity, model, property, belongsTo } from '#loopback/repository';
import { Game, GameWithRelations } from './game.model';
#model({ settings: { strict: 'filter' } })
export class Rank extends Entity {
#property({
type: 'string',
id: true,
})
id?: string;
#property({
type: 'string',
})
name?: string;
#property({
type: 'string',
})
shortName?: string;
#property({
type: 'string',
})
avatar?: string;
#belongsTo(() => Game)
gameId: string;
constructor(data?: Partial<Rank>) {
super(data);
}
}
export interface RankRelations {
game?: GameWithRelations;
}
export type RankWithRelations = Rank & RankRelations;
Game Model
import { Entity, model, property, embedsMany, hasMany } from '#loopback/repository';
import { Rank, RankWithRelations } from './rank.model';
import { HasMany } from 'loopback-datasource-juggler';
#model({ settings: { strict: 'filter' } })
export class Game extends Entity {
#property({
type: 'string',
id: true,
})
id?: string;
#property({
type: 'string',
required: true,
})
name?: string;
#property({
type: 'string',
})
shortName?: string;
#property({
type: 'string',
})
avatar?: string;
#hasMany<Rank>(() => Rank, { keyTo: 'gameId' })
ranks?: Rank[];
constructor(data?: Partial<Game>) {
super(data);
}
}
export interface GameRelations {
}
export type GameWithRelations = Game & GameRelations;
Game Controller
// in this method
// 500 Error: Invalid "filter.include" entries: {"relation":"ranks"}
#get('/games/{id}')
async findById(#param.path.string('id') id: string): Promise<Game> {
return await this.gameRepository.findById(id, { include: [{ relation: 'ranks' }] });
}
Please run your application with DEBUG=loopback:repository:relation-helpers, that way you will get a debug message explaining why filter.include entry was rejected.
You can find the code building the error message here:
https://github.com/strongloop/loopback-next/blob/97ba7893e253bfc2967ac08e408b211c9b9b7f40/packages/repository/src/relations/relation.helpers.ts#L96-L100
The most likely cause: your GameRepository does not have any InclusionResolver registered for ranks relation.
Please refer to our todo-list example to see how to register inclusion resolver. Cross-posting from https://github.com/strongloop/loopback-next/blob/97ba7893e253bfc2967ac08e408b211c9b9b7f40/examples/todo-list/src/repositories/todo-list.repository.ts#L41-L46:
this.todos = this.createHasManyRepositoryFactoryFor(
'todos',
todoRepositoryGetter,
);
this.registerInclusionResolver('todos', this.todos.inclusionResolver);

alias was not found. Maybe you forget to join it

In my nectjs project I'm using TypeORM and I have 2 entities user and post,
and I'm tying to make a relation between them
user.entity.ts
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column({ length: 50, unique: true })
name: string;
#OneToMany(type => Post, post => post.user)
posts: Post[];
}
post.entity.ts
#Entity()
export class Post {
#PrimaryGeneratedColumn()
id: number;
#Column({ length: 30 })
title: string;
#ManyToOne(type => User, user => user.posts)
user: User;
}
So I want to join these tables and get post by it's title for specific user
const PostObject = await createQueryBuilder("post")
.leftJoinAndSelect(
"post.user",
"user",
"post.title = :title",
{ title: "title1" }
)
.where("user.id = :id", { id: id })
.getOne();
but when I run the project and execute this function I get this error:
Error: "post" alias was not found. Maybe you forget to join it?
You have to inject the Repository first:
constructor(
#InjectRepository(Post) private readonly postRepository: Repository<Post>
) {
}
Then you can create the QueryBuilder for the Post entity by passing in the alias:
this.postRepository.createQueryBuilder('post')
// ^^^^^^^^^^^^^^^^
.leftJoinAndSelect(
'post.user',
'user',
'post.title = :title',
{ title: 'title1' },
)
.where('user.id = :id', { id })
.getOne();
Can also use getRepository like below.
const PostObject = await getRepository("post").createQueryBuilder("post")
.leftJoinAndSelect(
"post.user",
"user",
"post.title = :title",
{ title: "title1" }
)
.where("user.id = :id", { id: id })
.getOne();

Categories