nestjs / typeorm

TypeORM module for Nest framework (node.js) 🍇

Home Page:https://nestjs.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Support EntityRepository pattern with TypeORM ^0.3

frankmangone opened this issue · comments

Is there an existing issue that is already proposing this?

  • I have searched the existing issues

Is your feature request related to a problem? Please describe it

Not related to an issue - it's more of a loss of functionality from previous versions!

Describe the solution you'd like

I've used the Repository pattern with classes using the @EntityRepository decorator in the past a lot - and since adding typeorm 0.3 into the mix, that doesn't seem to be supported anymore.

I would love to request this feature to be added, if possible. I've implemented something that fits the needs of my project in particular, but I assume a lot of improvement could be made. This is what I've got so far:

import { Module } from '@nestjs/common'
import { TypeOrmModule } from '@nestjs/typeorm'
import { DataSource } from 'typeorm'

@Module({
  imports: [TypeOrmModule],
})
export class TypeOrmRepositoriesModule {
  static forFeature(Repositories: any[]) {
    const entities = Repositories.map((repo) => {
      return Reflect.getMetadata('entity', repo)
    })

    // Create base module as you'd normally do with TypeOrm
    const module = TypeOrmModule.forFeature(entities)
    const providers = module.providers

    // Override each provider with custom methods from Repositories
    providers.forEach((provider, index) => {
      const originalUseFactory = (provider as any).useFactory

      ;(provider as any).useFactory = (dataSource: DataSource) => {
        const originalRepo = originalUseFactory(dataSource)

        const Repo = Repositories[index]

        const extendedRepo = new Repo()
        const methodNames = Object.getOwnPropertyNames(
          Object.getPrototypeOf(extendedRepo)
        )

        methodNames.forEach((methodName) => {
          if (methodName === 'constructor') return
          // Maybe the method names from the original repo could be whitelisted here as well
          originalRepo[methodName] = extendedRepo[methodName]
        })

        return originalRepo
      }
    })

    module.providers = providers
    module.exports = providers

    return module
  }
}

The idea here is that we can define an EntityRepository class that extends Repository<Entity> (as with previous versions), and instead of using TypeOrmModule, we change it for TypeOrmRepositoriesModule so that it can be used as an import in this fashion (i.e. with User as the entity in question):

imports: [TypeOrmRepositoriesModule.forFeature([UsersRepository])],

We would then define the repository class as:

@EntityRepository(User)
export class UsersRepository extends Repository<User> {
  // Extend the base repo with:
  async findUnverified(): Promise<User[]> {
    return this.find({
      where: { verified: false },
    })
  }
}

And the EntityRepository decorator would be as simple as:

export const EntityRepository = (Entity) => {
  return Reflect.metadata('entity', Entity)
}

Again, this is a very simple implementation that fits the needs of my project, but I'm positive that if this is to be implemented, it would need some more polish :)

Teachability, documentation, adoption, migration strategy

I think users would pick this up pretty quickly, as it was part of the docs of previous versions of this repo. Furthermore, the original documentation can probably be recovered by the maintainers of this project, and I guess it would need little changes :)

What is the motivation / use case for changing the behavior?

Ease of use and extensibility of the repository pattern with additional method definitions.

For anyone looking for this, it's pretty similar to what's described in this gist. The solution proposed there works perfectly 😄