I have a Recipe and a Tag model. Currently, the recipe contains an array of id's belonging to Tag:
#Entity()
export class Recipe extends BaseEntity {
#PrimaryGeneratedColumn('uuid')
public id!: string;
#Column({ type: 'varchar' })
public title!: string;
#Column({ type: 'varchar' })
public description!: string;
#Column({ type: 'simple-array', nullable: true })
public tags: string[];
}
#Entity()
export class Tag extends BaseEntity {
#PrimaryGeneratedColumn('uuid')
public id!: string;
#Column({ type: 'varchar' })
public name!: string;
}
However, I am currently not making use of the relational capabilities of TypeORM. I was wondering though, how would i go about doing this? Since the relation only works one way, i.e. the one Recipe having many Tags.
I could be wrong, but I believe by default, you must declare both ways--even if you only intend to use a single direction of the relationship.
For example, you need to declare that a Recipe has many Tags you also have to set up the Tag to Recipe relationship even if you aren't going to use it.
Given your example, you'll need to set up a one:many and a many:one relationship.
Since Recipe will "own" the tags, it will have the one:many:
// recipe.entity.ts
#OneToMany(() => Tag, (tag) => tag.recipe)
tags: Tag[];
Then the inverse will look like this:
// tag.entity.ts
#ManyToOne(() => Recipe, (recipe) => recipe.tags)
#JoinColumn({
name: 'recipeId',
})
recipe: Recipe;
If you're considering having many recipes own the same tag, you may need to consider using a many:many relationship
EDIT
I suppose you could technically store an array of id's in a column to represent tags for any given recipe. The question here is, what happens if you decide you need further info on any given tag?
IMO, (and it's just that so take all of this with a grain of salt). You are bending your recipe table to also store relationship info.
I have found it to be more helpful to keep my "buckets" (tables) as specific as possible. That'd leave us with:
recipes | tags | recipes_tags
-----------------------------
That way my recipes table just has recipes & that's it. "Just give me all recipes...". Tags is the same, "just show me all tags"
The two things are completely different entities. By setting up a ManyToMany relationship, we're telling TypeORM that these two columns are related--without "muddying" either of their underlying data.
You can add/remove columns on the pivot table should you decide you want more info about the relationship. At that point, you'd still be working with the relationship, not a tag or recipe so your data would still be nice & lean!
Another example from one of my own use cases...
I have an Activity and a Resource any given resource can have one or more Activities. (activities = tags/ resources = recipes)
// activity.entity.ts
...
#PrimaryGeneratedColumn('uuid')
id: string;
#Column()
name: string;
...
#ManyToMany((type) => Resource, (resource) => resource.activities)
resources: Resource[];
// resource.entity.ts
#PrimaryGeneratedColumn('uuid')
id: string;
#Column()
name: string;
...
#JoinTable()
#ManyToMany((type) => Activity, (activity) => activity.resources)
activities: Activity[];
The above generates a resources_activities_activities table.
Within that table is:
resourceId | activityId
------------------------
I could add additional columns here as well. createdBy or status or something else that is specific to the relationship. Each entry in this table has a relationship back to the activity and the resource--which is great!
I realize we've gone outside the scope of your original question, but I think this is a pretty small step outside, for a potential big win later on.
When I make a request to get a resource: example.com/resources/123 I get something like this back:
"id": "123"
...
"activities": [
{
"id": "f79ce066-75ba-43bb-bf17-9e60efa65e25",
"name": "Foo",
"description": "This is what Foo is.",
"createdAt": "xxxx-xx-xxxxx:xx:xx.xxx",
"updatedAt": "xxxx-xx-xxxxx:xx:xx.xxx"
}
]
...
Likewise, any time I get an activity, I also get back any resources that are related to it. In my front-end I can then easily do something like resource.activities.
Related
Is it possible in TypeORM, PostgreSQL, AdminJs with One to Many table relationships to immediately receive data from the associated table, and not a link to this data, as in my example.
#Entity()
export class User extends BaseEntity {
#PrimaryGeneratedColumn()
id: number;
#Column({ unique: true })
userColum: string;
#OneToMany(() => Car, (car) => car.car)
cars: Car[];
}
Car:----------------------------
#Entity()
export class Car extends BaseEntity {
#PrimaryGeneratedColumn()
id: number;
#ManyToOne(() => User, (user: User) => user.userColum, { eager: true })
car: Car;
#Column()
name: string;
}
ТI get a link here:
#ManyToOne(() => User, (user: User) => user.userColum, { eager: true })
But I would like to pull up the data itself. This seems to require an additional request.
Or will you have to somehow custom make the request yourself?
If yes, how can this be done?
Next I use AdminJS to display data and act on tables
I'm using AdminJS for the front-end (This requirement is not made by me.
Therefore, I do not have routes, this is done by AdminJS under the hood.
I did not find any mention of my situation in the AdminJS documentation.
At the moment it turns out that instead of the desired data is the value from the userColum column, when I display the Cars table in a column called CarId, I get a reference to userColum. The link is displayed as an index of the Users table - 1, 2, 3 and is clickable.
Here, instead of displaying 1, 2, 3, I want to see the text value “userColum 1”, “userColum 2”, “userColum 3”, which are in the Users column userColum.
Accordingly, when I click on the link, I get the necessary data of the userColum column. And I want the data to be immediately displayed inside the Cars table.
If we remove { eager: true }
Then in the database in the Cars table in the CarId column there will be id from the Users table, but nothing is displayed on the front-end AdminJs.
I believe you have a mistake in your Car entity (and a possible bad practice in User entity)
the mistake - it should be
#ManyToOne(() => User, (user: User) => user.cars, { eager: true })
user: User; // not car: Car;
and bad naming
#OneToMany(() => Car, (car) => car.user) // not car.car
cars: Car[];
Also, it would be helpful to know how you are querying this data. According to the docs
you must specify the relation in FindOptions
Like so:
const userRepository = dataSource.getRepository(User)
const users = await userRepository.find({
relations: {
cars: true,
},
})
So, i'm new into this typeORM thing, and actually also new into postgresSQL DB, and there's something i couldn't undertand about typeORM and making relations between tables.
My Question: So, i have two entities, User and Post. When you create a post, we store the user ( creator of the post ) in the DB using #JoinColumn, and when i go to users table, i can see the name of that field (username), but, inside User entity, we have an array of Posts, but, that field doesn't appear in the postgres DB, so, when i create a relation, #ManyToOne and #OneToMany, what data stores in the DB and which don't ? Besides that, when i fetch stuff, i can fetch the array, but, does that array is store in the DB or what ? I'm kinda confused with this, so, now let me show you the code
User entity
import {
Entity as TOEntity,
Column,
Index,
BeforeInsert,
OneToMany
} from "typeorm";
import bcrypt from "bcrypt";
import { IsEmail, Length } from "class-validator";
import { Exclude } from "class-transformer";
import Entity from "./Entity";
import Post from "./Post";
#TOEntity("users")
export default class User extends Entity {
constructor(user: Partial<User>) {
super();
Object.assign(this, user);
}
#Index()
#IsEmail(undefined, { message: "Must be a valid email address" })
#Length(5, 255, { message: "Email is empty" })
#Column({ unique: true })
email: string;
#Index()
#Length(3, 200, { message: "Must be at leat 3 characters long" })
#Column({ unique: true })
username: string;
#Exclude()
#Length(6, 200, { message: "Must be at leat 3 characters long" })
#Column()
password: string;
#OneToMany(() => Post, post => post.user)
posts: Post[];
#BeforeInsert()
async hashedPassword() {
this.password = await bcrypt.hash(this.password, 6);
}
}
Post entity
import {
Entity as TOEntity,
Column,
Index,
BeforeInsert,
ManyToOne,
JoinColumn,
OneToMany
} from "typeorm";
import Entity from "./Entity";
import User from "./User";
import { makeid, slugify } from "../util/helpers";
import Sub from "./Sub";
import Comment from "./Comment";
#TOEntity("posts")
export default class Post extends Entity {
constructor(post: Partial<Post>) {
super();
Object.assign(this, post);
}
#Index()
#Column()
identifier: string; // 7 Character Id
#Column()
title: string;
#Index()
#Column()
slug: string;
#Column({ nullable: true, type: "text" })
body: string;
#Column()
subName: string;
#ManyToOne(() => User, user => user.posts)
#JoinColumn({ name: "username", referencedColumnName: "username" })
user: User;
#ManyToOne(() => Sub, sub => sub.posts)
#JoinColumn({ name: "subName", referencedColumnName: "name" })
sub: Sub;
#OneToMany(() => Comment, comment => comment.post)
comments: Comment[];
#BeforeInsert()
makeIdAndSlug() {
this.identifier = makeid(7);
this.slug = slugify(this.title);
}
}
How the User entity looks as a table in the DB
So, as you can see, there's no field with name posts ( which is weird, because as i already said, if i can fetch that, where is that data if i can't see it in the DB )
Now, let me show you Post entity
What i want to understand: So, we have the relationship between tables, know, i tried to search stuff in order to understand that, but i couldn't find anything, so, if you can help me with this mess, i would really aprecciate that, so, thanks for your time !
Let's take this section and try to understand piece by piece:
#ManyToOne(() => User, user => user.posts)
#JoinColumn({ name: "username", referencedColumnName: "username" })
user: User;
1. #ManyToOne(() => User, user => user.posts):
#ManyToOne: This annotation tells typeORM that Post entity is going to have a many to one relationship. From the postgres DB point of view, this means that posts table is going to have a new column (foreign key) which points to a record in some other table.
() => User: This is called definition of the target relationship. This helps typeORM to understand that the target of the relationship is User entity. For postgres DB, this means the foreign key in posts table is going to reference a row in users database
user => user.posts: This is called the inverse relationship. This tells typeORM that the related property for the relationship in User entity is posts. From the postgres DB point of view, this has no meaning. As long as it has the foreign key reference, it can keep the relationship between the two tables.
2. #JoinColumn({ name: "username", referencedColumnName: "username" }):
#JoinColumn: In this scenario, this annotation helps typeORM to understand the name of the foreign key column in posts table and the name of the referenced column in users table
name: "username": This is the name of the column in posts table which is going to uniquely identify a record in users table
referencedColumnName: "username": This is the name of the column in users table which is going to be referenced by the foreign key username in posts table.
inside User entity, we have an array of Posts, but, that field doesn't appear in the postgres DB
The array of Posts is there for the typeORM to return you an array of linked posts. It is not needed by postgres DB to contain the relationship.
when i create a relation, #ManyToOne and #OneToMany, what data stores in the DB and which don't
Whatever property you decorated using #Column will be there in the table as it is. And for the relationships, only the foreign key will be saved. As an example, when you save a Post entity, it will save only the relevant columns in that entity + username foreign key.
when i fetch stuff, i can fetch the array, but, does that array is store in the DB or what ?
When you query User entity, typeorm uses the annotations to join users table with posts table and return you the posts with the user you searched. But in database, it saves users and posts data in their respective tables and uses username foreign key to keep the relationship between them.
I hope this helps you to understand what happens. Cheers 🍻 !!!
I'm fairly new to Postgres (and Typeorm), and having a really hard time figuring out how to handle the following scenario:
I have a table called blog and a table called category . Blogs can belong to many categories, and categories can have many blogs. So I believe I need a "many to many" relationship here.
My join table needs to have a column called is_primary, so since I need an additional custom column, I believe I need to create my own entity for this, instead of using the auto-generated one. I'm calling this table blogCategory. I'm getting tripped up here on how to handle the save when I need to set the is_primary flag to true.
Here is my schema:
// blog.entity.ts omitting some fields for brevity
#Column()
#PrimaryGeneratedColumn()
id: number;
#Column()
title: string;
#OneToMany(() => BlogCategory, blogCategory => blogCategory.blog)
blogCategories: BlogCategory[];
// category.entity.ts
#Column()
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#OneToMany(() => BlogCategory, blogCategory => blogCategory.category)
blogCategories: BlogCategory[];
// blog-category.entity.ts
#PrimaryGeneratedColumn()
id: number;
#Column()
is_primary: boolean;
#Column()
categoryId: number;
#Column()
blogId: number;
#ManyToOne( () => Category, category => category.blogCategories )
category: Category
#ManyToOne( () => Blog, blog => blog.blogCategories )
blog: Blog
I think that is all correct, but please correct me if I'm wrong. But the part I'm getting tripped up on now is how I actually handle the blog insert. I'm not sure how I handle adding the is_primary flag when necessary. Do I actually insert the blogCategory item manually? In which case I'd need to save the blog first, so that I'd have access to its id for the blogCategory. I was kind of under the impression that item would be created automatically.
Thanks in advance for anyone who is able to offer some insight!
You can achieve automatic insertion using {cascade: true} in your entity relation. Check this post How to save relation in #ManyToMany in typeORM
Other way you can do it using transaction.
First save Blog, get the blog id then add item into BlogCategory
See https://typeorm.io/#/transactions
For an example see this pseudo code,
await getManager().transaction(async transactionalEntityManager => {
const blog = await transactionalEntityManager.save(blogObject);
blogCategory.blogId = blog.id;
blogCategory.CategoryId = categoryId; // get categoryId from request body
await transactionalEntityManager.save(blogCategory);
});
I have a NestJs REST API and use TypeORM with MongoDB. I want to create a entity called project. I started with a basic entity and I just read that I should use ObjectIdColumn instead of PrimaryColumn for MongoDB.
#Entity()
export class Project extends BaseEntity {
// The technical project name
#ObjectIdColumn({ generated: false })
public id: ObjectID;
// The display name
#Column({ unique: true })
public name: string;
// The project successor
#Column({ nullable: true })
public successorId: ObjectID;
// Configuration stuff for that project
#Column()
public configuration: object;
}
I would like to know if it's possible to replace that object id column with a primary column of type string. The id field is based on a special pattern, e.g. the name field
my awesome project
would result into
my-awesome-project
for the id field. Sure I made use of generated: false but I have to pass in a custom string instead of an ObjectID. Currently this is not possible because the docs say the ObjectID
Can be a 24 byte hex string, 12 byte binary string or a Number. http://mongodb.github.io/node-mongodb-native/2.1/api/ObjectID.html
So what needs to get done to use a custom string as an ID field? The only thing I can think of is creating a second field e.g. theRealId and treat it like the ID field and ignore the autogenerated ObjectId...
From what I've learnt, here is what you can do
#Entity()
export class UserEntity
{
#ObjectIdColumn()
_id: string;
#PrimaryColumn()
id: string;
// The display name
#Column({ unique: true })
public name: string;
#Column({ nullable: true })
public successorId: ObjectID;
#Column()
public configuration: object;
}
MongoDB will use _id as an internal id, that you do not expose through your program (and api, then)
You will work with the id, "normally', and it will be your primary key, generating automatically and so on
Source : personal learning, and Udemy course : NestJS Zero to Hero - Modern TypeScript Back-end Development
First off I want to say this question is similar to this one which references this one. I have the exact same question as the second link except a notable difference. I'm trying to extend a class generated by NestJS which defines a property.
I'm using NestJs with the Schema first approach found here. I'm also generating a classes file based on my GraphQL Schema.
Here is the Schema:
type Location {
name: String!
owner: User!
}
Which generates the class:
export class Location {
name: string;
owner: User;
}
Now, I want to extend this class so I don't have to repeat the data (there are a lot more fields not shown). I also I want to add fields that live on a document but are not in the schema (_id in this example). Here is my LocationDocument and my schema.
export interface LocationDocument extends Location, Document {
_id: Types.ObjectId
}
export const LocationSchema: Schema = new Schema(
{
name: {
type: String,
required: true,
},
owner: {
type: Types.ObjectId,
ref: 'User',
}
);
Now here is my issue. The generated Location class from the GraphQL schema defines the owner property as a User type. But in reality it's a just a mongodb id until it is populated by Mongoose. So it could be a Types.ObjectId or a User on a UserDocument. So I attempted to define it as:
export interface LocationDocument extends Location, Document {
_id: Types.ObjectId
owner: User | Types.ObjectId;
}
But this throws an error in the compiler that LocationDocument incorrectly extends Location. This makes sense. Is there any way to extend the User Class but say that owner property can be a User Type (once populated by Mongoose) or a mongo object ID (as is stored in the database).
I decided that having a property that can be both types, while easy with Mongoose and JS, isn't the typed way. In my schema I have an owner which is a User type. In my database and the document which extends it, I have an OwnerId. So to people accessing the API, they don't care about the ownerId for the relationship. But in my resolver, I use the Id. One is a Mongo ID type, the other is a User type.