Although the TypeORM official documentation gives the answer to this question, looks like there are some pitfalls. In my case, there are not errors, but looks like the binding has not been executed correctly.
Well, I can't do exactly as in the TypeORM documentation in the code for production, so I have collected the data for diagnostic.
In my case, the BlogPost
entity depends on BlogPostCategory
:
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne as ManyToOne, Relation } from "typeorm";
import { isUndefined } from "@yamato-daiwa/es-extensions";
@Entity()
export default class BlogPost {
@PrimaryGeneratedColumn("uuid")
public readonly ID!: BlogPost.ID;
@Column({ type: "varchar", nullable: false })
public readonly heading!: string;
@Column({ type: "text", nullable: false })
public readonly HTML!: string;
/* [ Theory ] About `Relation` https://stackoverflow.com/a/71983552/4818123 */
@ManyToOne(
(): typeof BlogPostCategory => BlogPostCategory,
(blogPostCategory: BlogPostCategory): ReadonlyArray<BlogPost> => blogPostCategory.blogPosts
)
public readonly category!: Relation<BlogPostCategory>;
@Column({ type: "varchar", nullable: false })
public readonly metaDescription!: string;
@Column({ type: "timestamp with time zone", nullable: false })
public readonly publishingDateTime__ISO8601!: string;
public constructor(
properties?: Readonly<Omit<BlogPost, "ID">>
) {
if (isUndefined(properties)) {
return;
}
this.heading = properties.heading;
this.HTML = properties.HTML;
this.category = properties.category;
this.metaDescription = properties.metaDescription;
this.publishingDateTime__ISO8601 = properties.publishingDateTime__ISO8601;
}
}
@Entity()
@Unique([ "text" ])
export default class BlogPostCategory {
@PrimaryGeneratedColumn("uuid")
public readonly ID!: BlogPostCategory.ID;
@Column({ type: "varchar", nullable: false })
public text!: string;
@OneToMany(
(): typeof BlogPost => BlogPost,
(blogPost: BlogPost): BlogPostCategory => blogPost.category
)
public blogPosts!: Array<BlogPost>;
public constructor(properties?: Readonly<Omit<BlogPostCategory, "ID">>) {
if (isUndefined(properties)) {
return;
}
this.text = properties.text;
}
}
Although I don't know the seeding library compatible with newest version of TypeORM,
in fact, the seeder is the simple script which could be run by node
or ts-node
, as in my case.
From the viewpoint of logic, because the BlogPost
depends on BlogPostCategory
, it is better to create the instances of BlogPostCategory
first.
import { getArrayElementSatisfiesThePredicateIfSuchElementIsExactlyOne } from "@yamato-daiwa/es-extensions";
import { DataSource } from "typeorm";
const dataSource: DataSource = new DataSource(/* ... */);
await dataSource.initialize();
// ...
const blogPostCategories: ReadonlyArray<BlogPostCategory> = await dataSource.manager.save(
Object.
values(initialBlogPostsCategories).
map(
(blogPostCategory): BlogPostCategory => new BlogPostCategory({ text: blogPostCategory.text })
)
);
console.log("=== CHECKPOINT 1 =========================================================================")
console.log(blogPostCategories);
const blogPosts: Array<BlogPost> = await dataSource.manager.save(
initialBlogPosts.map(
(blogPost: Readonly<Omit<BlogPost, "ID">>): BlogPost =>
new BlogPost({
...blogPost,
category: getArrayElementSatisfiesThePredicateIfSuchElementIsExactlyOne(
blogPostCategories,
(blogPostCategory: BlogPostCategory): boolean =>
blogPostCategory.text === blogPost.category.text,
{ mustThrowErrorIfElementNotFoundOrMatchesAreMultiple: true }
)
})
)
);
console.log("=== CHECKPOINT 2 =========================================================================")
console.log(blogPosts)
The example of output at CHECKPOINT 1
:
[
BlogPostCategory {
ID: '1c23cf90-f112-442f-9c12-240d6749d26d',
text: 'Category 1',
blogPosts: undefined
},
BlogPostCategory {
ID: 'f433eeb3-22e2-4218-81ed-d3ff21fe4936',
text: 'Category 2',
blogPosts: undefined
}
]
blogPosts
are still undefined because they has not been created yet.
I suppose, it is fine for now.
The example of output at CHECKPOINT 2
:
[
BlogPost {
ID: 'e0403e10-4659-4363-88e7-8e7529823b31',
heading: 'Post 1',
HTML: "<h1>Post 1</h1>",
category: BlogPostCategory {
ID: 'ae525873-91dc-4b84-92cc-ea31dcd326cb',
text: 'Category 1',
blogPosts: undefined
},
metaDescription: 'Post 1',
publishingDateTime__ISO8601: '2024-01-25T07:23:59.840Z'
},
BlogPost {
ID: '5f7dd7de-82c6-4119-8653-288d7e20a79f',
heading: 'Post 2',
HTML: "<h2>Post 2</h2>",
category: BlogPostCategory {
ID: '1c23cf90-f112-442f-9c12-240d6749d26d',
text: 'Category 2',
blogPosts: undefined
},
metaDescription: 'Post 2',
publishingDateTime__ISO8601: '2023-12-31T15:00:00.000Z'
}
]
We have the recursion: BlogPost
has BlogPostCateogry
inside, and BlogPostCateogry
has BlogPost
s. I am not sure about it is fine, but when I try to retrieve the blog posts from the database, they have empty category. I suppose, the problems begins here.
The database content:
I suppose the usage of constructors in the entities is not the cause, because according to TypeORM documentation, the usage of constructors is fine if the constructor parameters are optional.
Hi Takeshi as per the documentation of TypeORM (Check Documentation), there will be a constraint created on one of the tables (BlogPost) and to load data along with the relations we need to pass extra options to the find query.
Like this
const blogCategoriesWithBlogs = await dataSource.manager.find(BlogPostCategory, {
relations: {
blogPosts: true,
}});
With the above query, we can get the blog posts associated with that category. Hope this helps.