Just another programming blog

HowTo, Tips&Tricks, Uncategorized

Refactorable relations with TypeORM

TypeORM classic find with relations example

If you’re familiar with TypeORM, you have probably seen code similar to below multiple times already:

userRepository.find({
    relations: [
        "profile",
        "photos",
        "videos",
        "videos.video_attributes",
    ],
});

What’s wrong with it?

It is common, yet not perfect, because of non-refactorable way of passing relations via strings array.

Consider changing the name of the videos property to movies in the entity itself. You will have to update that name in every spot you have asked for that relation. It may be really hard to not overlook anything.

Not only your IDE cannot help you with refactoring, but compilation will go completely fine! Not even a warning. Until you’ll try to run the particular piece of code which is now simply outdated and not working.

Maintaining a bigger project with multiple developers can lead to many situations like this.

How to improve it?

One solution came to my mind while I was thinking about well-known ‘nameof’ keyword from C#, which is somewhat what we are looking for in here. Since we want to get even nested relations, it’s a bit more tricky, but seems to work well (tested in production already) and to fully satisfy my needs. The method itself may look like this:

protected nameof<T extends object>(
    fn: ((obj: T) => any) | (new (...params: any[]) => T)): string {
    const fnStr: string = fn.toString();

    // It supports only arrow functions, i.e.:
    // 'x => x.prop'
    if (!fnStr.includes('=>')) {
        throw new Error('nameof: Invalid function.');
    }

    return fnStr
        .substring(fnStr.indexOf('.') + 1) // Parse name
        .replace(/[?!]/g, '') // Clean assertion operators
        .replace(/\[[0-9]+\]/g, ''); // Clean arrays
}

// Note: above method is highly inspired by this package, but adds arrays support and simplifies stuff to be tailored for our case.

This method simply stringifies a method we pass as a parameter and operates on the resulted string to generate relation string we previously tended to write implicitly. In details: it omits all characters before first dot and then removes both assertion operators and property accessors (which we have to use to operate on nested types with arrays).

// Note: I have put this method in BaseRepository class in my project, since I keep all data layer behind my custom repositories to separate TypeORM references and this is why the method is protected.

Result

Use of that method in our example case results in the following:

userRepository.find({
    relations: [
        this.nameof((x) => x.profile),
        this.nameof((x) => x.photos),
        this.nameof((x) => x.videos),
        this.nameof((x) => x.videos[0].video_attributes),
    ],
});

This way it is now refactorable!

Your IDE is going to give you a better code completion and even just a compiler alone will blast errors if something remained unchanged.

Hope it will save you some troubles in the future. Have healthy and non-problematic relations 😉

Leave a Reply