Relation on #PrimaryColumn having also #PrimaryGeneratedColumn not involved - nestjs

I am using NestJS with Typeorm recipe and I am a newbie in both of them. We are migrating from Loopback3, where all of these were implemented with the basic features of the framework.
We have the following scenario for a lot of relations among our entities:
Entity A with "id" and "code" fields, both of them are primary, but only "id" is auto-generated, "code" comes from other logics and it is always guaranteed to be unique.
Entity B with "a_code" field that is a foreign key for entity A.
We have in class A:
#PrimaryGeneratedColumn()
id: number;
#Column()
#PrimaryColumn()
code: string;
#OneToMany(() => B, (b: B) => b.a_code)
a_list: A[];
With controller:
#Crud({
model: {
type: A
},
query: {
join: {
a_list: {
eager: true,
},
'a.a_list': {
eager: true,
alias: 'a_list',
}
}
}
})
And for class B:
#PrimaryGeneratedColumn()
id: number;
#Column()
a_code: string;
#ManyToOne(() => A, a => a.a_list) a: A;
For legacy reasons we need to keep both of these fields because these objects can be frequently removed and reinserted (changing the id in the meantime) leaving the relations "pending" with no impact on the business logic we have.
I noticed that syncing the database, what it's created, is of course an index in the format of (id, code), but the same is for the foreign key B=>A, that's not what I look for.
It's ok for me to have a generated primary key (id) and a unique field (code) to have the relation on the latter.
What I am trying to figure out is the best way to do this in Typeorm, and if there is a legit way to do it.

Related

Is it semantically correct to use ID scalar type for a foreign key field in a Type-graphql and Typeorm entity?

Here's the definition of String and ID from GraphQL's documentation:
GraphQL comes with a set of default scalar types out of the box:
String: A UTF‐8 character sequence.
ID: The ID scalar type represents a unique identifier, often used to refetch an object or as the key for a cache. The ID type is serialized in the same way as a String; however, defining it as an ID signifies that it is not intended to be human‐readable.
Would you use the ID type for the userId field in Article, which is the foreign key to the User entity? Or would you prefer String type for userId because the id field is the unique identifier for Article, not userId?
#ObjectType()
#Entity()
export class Article {
#Field((type) => ID)
#PrimaryGeneratedColumn("uuid")
id: string;
#Field()
#Column({ type: "text" })
title: string;
#Field(type => ID) // <<= ID or String?
#Column({type: "uuid"})
userId: string
#ManyToOne((type) => User, (user) => user.id)
#JoinColumn({ name: "userId" })
user: User;
#Field()
#CreateDateColumn()
created: Date;
#Field()
#UpdateDateColumn()
updated: Date;
}
#ObjectType()
#Entity()
export class User {
#Field((type) => ID)
#PrimaryGeneratedColumn("uuid")
id: string;
#Field()
#Column({ type: "text" })
name: string;
}
Semantically the scalar ID makes more sense than the scalar String since per the definition it represents a unique identifier, often used to refetch an object or as the key for a cache. It is also not meant for human consumption, another part of the definition of the scalar ID.
The uniqueness of it is not really important from a GraphQL perspective it is very common to see multiple IDs in the same type. Using it is in fact encourage has it indicates to your consumer that it can (most likely) be used to fetch the full object.
Now you also have to challenge the need to expose the userId at all since this is a very "REST" way of thinking and it leaks the database relational model used, making it harder to evolve your data model without making breaking changes to the schema.
Generally you would only expose the user: User field as it already contains an id field. One might argue that it would do an unnecessary join, but this could be easily optimized with a lookahead. If a consumer needs user data, they will likely need more than just the id anyway so they will have to fetch the whole object from the database.

NestJS TypeORM OneToMany column not displayed in db

I've setup a OnetoMany relationship between table "Movie" and "Like". And a ManyToOne relationship between table "Like" and "Movie". In the like table I can see that there's a column created 'movieID' that links the row to a movie just as expected. However there's no "likes" column created in the Movie table that should indicate the relationship.
import { Like } from './like.entity';
#Entity()
export class Movie {
#PrimaryGeneratedColumn()
id: number;
#Column()
title: string;
#OneToMany(() => Like, (like) => like.movie)
likes: Like[];
}
import { Movie } from './movie.entity';
#Entity()
export class Like {
#PrimaryGeneratedColumn()
id: number;
#Column({ default: 0 })
likes: number;
#ManyToOne(() => Movie, (movie) => movie.likes)
movie: Movie;
}
A OneToMany/ManyToOne relationship only needs an entry in the table representing the Many part. It is sufficient to know which likes belong to a movie in order to deduce which likes a movie has. Maintaining the information in both tables would be inefficient and error-prone. The behavior is as expected from an ORM.

TypeORM. Two Foreign Keys referencing the same Primary Key in one table

In my project I would like to have bi-directional ManyToOne - OneToMany relations with two foreign keys referencing to the same primary key. In my case it would be a table 'match' which contains two players from table 'player' (player1Id and player2Id are FK). I want to be able to get all matches where a particular player played as well as assign a player to the match. In Match entity I guess it should be something like this:
#Entity()
export class Match {
#PrimaryGeneratedColumn()
id!: number;
#ManyToOne((type) => Player)
#JoinColumn({ name: "player1Id", referencedColumnName: "id" })
player1: Player;
#ManyToOne((type) => Player)
#JoinColumn({ name: "player2Id", referencedColumnName: "id" })
player2: Player;
//some other properties...
but since I have to indicate one inverse-side entity in #OneToMany() decorator then how should it look like in Player entity? Is there any way to map such an association in TypeORM and is it a good and common practice to have two FK in one table referencing to the same primary key in another table? I'm new in NodeJS and webdev in general. Thanks for any help.
This looks to me like you have a many to many relation here. Match has many players (even if you only need two, and this this you could always scale the number of players), and a player can be at many matches.
#Entity()
export class Match {
#PrimaryGeneratedColumn()
id!: number;
#ManyToMany((type) => Player, (player) => player.matches)
players: Player[];
//some other properties...
The player entity. If you do indeed make the relation many to many you must place #JoinTable() at one of the entities.
#Entity()
export class Player{
#PrimaryGeneratedColumn()
id!: number;
#ManyToMany((type) => Match, (match) => match.players)
#JoinTable()
matches: Match[];
//some other properties...

NestJS Rest API show only some columns from database using TypeORM

I would like to show only some of the columns in the Entity using NestJS and TypeORM. These are my columns in the Entity:
#Entity({ name: "products" })
export class Product extends BaseEntity {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#Column()
quality: number;
#Column()
color: string;
#Column()
desc: string;
My repository.ts:
#EntityRepository(Product)
export class ProductRepository extends Repository<Product> {}
For example is someone wants to open products/specific it should show only the name and desc.
This is my function in services.ts now:
async get(productid: number): Promise<Product> {
var product: Product= await this.productRepositroy.findOne(productid);
return product;
}
How should I modify it to return only the chosen Columns? I have read about QueryBuilders, but I have no idea how i should implement them.
Your repository has inherited from the method createQueryBuilder by extending Repository. Thus, you can use it with select in order to only get the expected columns:
const product = await this.productRepository
.createQueryBuilder('p')
.where('p.productId = :productId', { productId })
.select(['p.name', 'p.desc']);
.getOne();
Additionally, Nest is able to modify your outgoing objects by using a Serializer: serialization doc. But this is always a good idea to only select the required columns.
NB: I'd also advise not mixing your business logic (here, handling "products") with your database too much (you'd better split the responsibility of Getting a product and Fetching data from the database, for testability/maintainability reasons).

Is it possible to join a table in Sequelize without defining a relationship?

My research suggests no. Here is a quick example of what I'd like to do.
Given 3 tables: Company, Product, Lookup...
Company has many Products. Products have one Company. Lookup table serves as an enum/constant for hardcoded values. I.e. state/country names, application specific naming conventions, etc.
Here are the models in sequelize-typescript (though the question still fully relates to sequelize js):
// COMPANY
#Table
export default class Company extends Model<Company> {
#PrimaryKey
#Column
Oid:number;
#Column
Name:string;
#Column
Address:string;
#HasMany(() => Product)
products: Product[];
}
// PRODUCT
#Table
export default class Product extends Model<Product>{
#PrimaryKey
#Column
Oid: number;
#ForeignKey(() => Company)
#Column
companyOid: number;
#BelongsTo(() => Company)
company: Company;
#Column
Price: number;
#Column
Name: string;
//#ForeignKey(() => Lookup) //attempt #1
//#Column({references: {model: "Lookup", key: "Oid"}}) //attempt #2
#Column
LkpStateOid: number;
}
// LOOKUP
#Table
export default class Lookup extends Model<Lookup> {
#PrimaryKey
//#BelongsTo(() => Product) // do not want to hardcode Product, as it could be any table
#Column
Oid:number;
#Column
Value:number; // where the value represents the hardcoded state, county, etc., ie "CA"
}
The issue here is that there is no "real" relationship between Product.lkpStateOid and Lookup.oid, except that one references the other in order to obtain the Lookup.value. In any sql variant, this is not an issue- just a simple join on the tables. In sequelize, however, the relationship between the tables must be known before I can get any of the associated data in a query. This is what I'd like to do:
const companyInfo = await Db.Company.find({
include: [{
model: Product,
include: [{
model: Lookup,
attributes: ["value"]
}]
}]
})
The first include is no problem. The second, nested include is not successful. I've tried a number of different annotations on the table, but the secondary issue (even if i could successfully "fake" a relationship in order to be able to associate the data) is that I do not want to hardcode which table the Lookup table belongsTo because the Lookup table could contain values that are needed in any number of other tables. Is there a way to accomplish this goal?
This link seems close to what I'd like (and is commented as an idea in the above table): http://docs.sequelizejs.com/manual/tutorial/associations.html#enforcing-a-foreign-key-reference-without-constraints
Any help would be appreciated. Thanks!
Yes, it is possible, because Sequelize accepts literal sequel queries in the form of sequelize.query(). You can totally bypass the include[] syntax (I haven't tested this yet, but it is my understanding). Just put a raw SQL query with a JOIN statement inside sequelize.query().
http://docs.sequelizejs.com/manual/tutorial/raw-queries.html

Resources