Summary
Make anyorm
to be real typeorm
.
safe-typeorm is a helper library of typeorm
, enhancing type safety like below:
- When writing SQL query,
- Errors would be detected in the compilation level
- Auto Completion would be provided
- Type Hint would be supported
- You can implement App-join very conveniently
- When SELECTing for JSON conversion
- App-Join with the related entities would be automatically done
- Exact JSON type would be automatically deduced
- The performance would be automatically tuned
AnyORM
The most famous ORM library in TypeScript
is typeorm
. However, Korean TypeScript backend developers jokingly call the TypeORM as AnyORM
. It's because the typeorm
does not guarantee type safety, so that cause lots of critical runtime errors.
As you can see from below code, JOIN or SELECT queries are written as a string typed value, and it never be validated in the compilation level. TypeScript developers can identify error only when running the backend server code.
Such type unsafety is the reason why I've developed safe-typeorm, a wrapper library typeorm to make it from AnyORM
to be real typeorm
.
class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToMany(() => Photo, (photo) => photo.user)
photos: Photo[];
}
const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.leftJoinAndSelect("user.photos", "photo") // unsafe
.andWhere("user.id = :id", { id: 1 }) // fuXXing unsafe
.getMany();
Prisma
When I've completed the safe-typeorm library, another ORM library prisma
has been newly inveted and it was much elegant than typeorm
. As the prisma
ensures perfect type safety and much convenient to use than typeorm
, I also moved from typeorm
to prisma
.
Therefore, no reason to maintain the safe-typeorm, due to I'm not using typeorm
more.
Revival
However, unlike my case abandoned typeorm
and adapted prisma
, many companies have been suffering by AnyORM
(typeorm
and its type unsafety). One of such company traveled my Github Account (https://github.com/samchon) and unearthed the safe-typeorm. The company requested me like below:
We have been suffering from type unsafety of TypeORM. We had wrapped the TypeORM to our self-developed module for enhancing type safety, but it had been too hard works for us. In nowadays, we've found your safe-typeorm library, and it seems like the solution what we've looked for. Can you write a Guide Documents?
I was a little bit surprised and could hear lots of voices from another companies (using NestJS in South Korea). What they had in common was that they are suffering from AnyORM
, but it was too risky to migrate their currently running commercial backend server to prisma
. Most of them are dreaming to prisma
, but it is not possible by legacy code.
Thus, I urgently wrote a Guide Documents. It took 2 years after the completion of safe-typeorm library that the Guide Documents was created. It is a pleasure story for me that there're some people who want my old libraries, but I also feel regretful that I left it for two years because I couldn't think of such specific requirements.
- Contents of Guide Documents written in two days:
- Relationships
- Builders
- Insertions
- Utilities
Enhance Type Safety
JoinQueryBuilder
With safe-typeorm, you can easily construct SQL queries, just by utilzing auto-completion.
If you've taken a mistake during the SQL query construction, compilation error will help you.
- When writing SQL query,
- Errors would be detected in the compilation level
- Auto Completion would be provided
- Type Hint would be supported
AppJoinBuilder
typeorm
does not support complicate application level join.
However, safe-typeorm does.
- You can implement App-join very conveniently
JsonSelectBuilder
JSON conversion with automatic query construction and performance tuning.
When you want to convert DB records to JSON data with a specific type (maybe DTO), you don't need to write any SQL query like SELECT
or JOIN
. Just by listing up neighborhoold entities to join and columns to use, safe-typeorm will construct optimal SQL queries and (application level) joining plan by itself.
- When SELECTing for JSON conversion
- App-Join with the related entities would be automatically done
- Exact JSON type would be automatically deduced
- The performance would be automatically tuned
import safe from "safe-typeorm";
export async function demo_app_join_builder(
groups: BbsGroup[],
): Promise<IBbsGroup[]> {
const builder = new safe.JsonSelectBuilder(BbsGroup, {
articles: new safe.JsonSelectBuilder(BbsArticle, {
group: safe.DEFAULT,
category: new safe.JsonSelectBuilder(BbsCategory, {
parent: "recursive" as const,
}),
tags: new safe.JsonSelectBuilder(
BbsArticleTag,
{},
(tag) => tag.value, // OUTPUT CONVERSION BY MAPPING
),
contents: new safe.JsonSelectBuilder(BbsArticleContent, {
files: "join" as const,
}),
}),
});
return builder.getMany(groups);
}
Safe Insertions
initialize
When creating a new entity instance, typeorm
cannot detect the omission of required properties. However, safe-typeorm supports safe entity instance creation by initialize()
function.
import * as orm from typeorm;
import safe from "safe-typeorm";
/**
* No error when compliation.
*
* However, becomes runtime error due to `writer` property omission.
*/
async function new_bbs_article(group: BbsGroup): Promise<BbsArticle> {
const article = new BbsArticle();
await article.group.set(group);
await article.category.set(null);
// article.writer = "Samchon";
article.ip = "127.0.0.1";
article.created_at = new Date();
return article;
}
/**
* Type safe factory function.
*
* Compilation error occurs due to `writer` property omission.
*/
function initialize_bbs_article(group: BbsGroup): BbsArticle {
return safe.initialize(BbsArticle, {
group: group,
category: null,
// writer: "Samchon",
ip: "127.0.0.1",
created_at: new Date(),
deleted_at: null,
});
}
InsertCollection
When inserting multiple records of multiple entities, you have to consider their dependency relationships when using typeorm
. However, with safe-typeorm, you don't need to consider such complicate dependency relationships. safe-typeorm will automatically analyze dependency relationships and insert records in the right order.
import safe from "safe-typeorm";
async function insert(
tags: BbsArticleTag[],
articles: BbsArticle[],
contents: BbsArticleContent[],
groups: BbsGroup[],
contentFiles: BbsArticleContentFile[],
categories: BbsCategory[],
files: AttachmentFile[],
): Promise<void> {
// although you've pushed entity records
// without considering dependency relationships
const collection: safe.InsertCollection = new safe.InsertCollection();
collection.push(tags);
collection.push(articles);
collection.push(contents);
collection.push(groups);
collection.push(contentFiles);
collection.push(categories);
collection.push(files);
// `InsertCollection` would automatically sort insertion order
// just by analyzing dependency relationships by itself
await collection.execute();
}
EntityUtil
With EntityUtil
class of safe-typeorm, you can merge duplicated records safely, even if target entity has complicate dependency relationships. The safety would be always keeped even when unique key constraint exists in the dependency relationships.
import * as orm from "typeorm";
import safe from "safe-typeorm";
async function unify(
original: Manufacturer,
duplicates: Manufacturer[],
): Promise<void> {
await safe.EntityUtil.unify(original, duplicates);
// manufacturers would be unified
// products would be unified
// product images would be unified
}
@orm.Entity()
class Manufacturer {
id: string;
name: string;
}
@orm.Entity()
@orm.Unique(["manufacturer_id", "name"])
class Product {
id: string;
manufacturer: safe.Belongs.ManyToOne<Manufacturer, "uuid">;
name: string;
}
@orm.Entity()
@orm.Unique(["product_id", "name"])
class ProductImage {
id: string;
product: safe.Belongs.ManyToOne<Product, "uuid">;
name: string;
url: string;
}
Conclusion
When adapting ORM library of TypeScript, I recommend to use prisma
.
However, if your legacy project is dependent on typeorm
, what about adapt safe-typeorm?
Top comments (0)