What are the steps for creating a custom repository with TypeORM (MongoDB) in NestJS?

One query that arises is regarding the @EntityRepository decorator becoming deprecated in typeorm@^0.3.6. What is now the recommended or TypeScript-friendly approach to creating a custom repository for an entity in NestJS? Previously, a custom repository would be structured like this:

// users.repository.ts
import { EntityRepository, Repository } from 'typeorm';
import { User } from './user.entity';

@EntityRepository(User)
export class UsersRepository extends Repository<User> {
  async createUser(firstName: string, lastName: string): Promise<User> {
    const user = this.create({
      firstName,
      lastName,
    });

    await this.save(user);

    return user;
  }
}

Since NestJS inherently supports TypeScript, calling usersRepository.createUser() in a service like below should work seamlessly:

// users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from './user.entity';
import { UsersRepository } from './users.repository';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(UsersRepository)
    private readonly usersRepository: UsersRepository,
  ) {}

  async createUser(firstName: string, lastName: string): Promise<User> {
    return this.usersRepository.createUser(firstName, lastName);
  }
}

The custom repository would be imported into modules as shown below:

// users.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersController } from './users.controller';
import { UsersRepository } from './users.repository';
import { UsersService } from './users.service';

@Module({
  imports: [TypeOrmModule.forFeature([UsersRepository])],
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}

My mention of MongoDB stems from the experience of encountering an error while using

<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="70040900151f021d30405e42">[email protected]</a>
, where @EntityRepository is still supported. However, upon attempting to import it in the module, I faced an error stating Repository not found or similar. Interestingly, this issue was not present when configuring TypeORM with postgresql. After discovering the deprecation of @EntityRepository, I couldn't find any relevant examples in the NestJS documentation.

Answer №1

After delving into the issue, I believe I have found a solution that allows for calling both custom and inherited methods. While this topic may not be widely discussed yet, there are some discussions happening on the typeorm GitHub threads: https://github.com/typeorm/typeorm/issues/9013

The solution showcased below utilizes MySQL as the underlying database driver, but it is anticipated to be compatible with MongoDB as well.

team.repository.ts

import {DataSource, Repository} from 'typeorm';
import {Injectable} from '@nestjs/common';
import {Team} from '@Domain/Team/Models/team.entity';

@Injectable()
export class TeamRepository extends Repository<Team>
{
    constructor(private dataSource: DataSource)
    {
        super(Team, dataSource.createEntityManager());
    }

    /**
     * Implements a basic where clause in the query and retrieves the first result.
     */
    async firstWhere(column: string, value: string | number, operator = '='): Promise<Team | undefined>
    {
        return await this.createQueryBuilder()
                         .where(`Team.${column} ${operator} :value`, {value: value})
                         .getOne();
    }
}

team.service.ts

import {Injectable} from '@nestjs/common';
import {Team} from '@Domain/Team/Models/team.entity';
import {TeamRepository} from '@Domain/Team/Repositories/team.repository';

@Injectable()
export class TeamService
{
    constructor(
        private teamRepository: TeamRepository,
    )
    {
    }

    async create(): Promise<Team>
    {
        const team: Team = await this.teamRepository.firstWhere('id', 1);

        return this.teamRepository.save(team);
    }
}

team.module.ts

import {Module} from '@nestjs/common';
import {TeamService} from '@Domain/Team/Services/team.service';
import {TypeOrmModule} from '@nestjs/typeorm';
import {Team} from '@Domain/Team/Models/team.entity';
import {TeamRepository} from '@Domain/Team/Repositories/team.repository';

@Module({
            imports:   [TypeOrmModule.forFeature([Team])],
            exports:   [TeamService],
            providers: [TeamService, TeamRepository],
        })
export class TeamModule
{
}

Answer №2

To set up a custom repository for MongoDB in TypeORM, follow these steps:

users.repository.ts

Instead of using @EntityRepository, utilize the @Injectable decorator and the MongoRepository for injecting the schema.

// users.repository.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { MongoRepository } from 'typeorm';
import { User } from './user.entity';
@Injectable()
export class UsersRepository {
  constructor(
    @InjectRepository(User)
    private readonly usersRepository: MongoRepository<User>,
  ) {}

  async createUser(firstName: string, lastName: string): Promise<User> {
    const user = new User({
      firstName,
      lastName,
    });

    await this.usersRepository.save(user);

    return user;
  }
  

   //include other useful methods here (find, delete, etc...)


}

users.service.ts

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from './user.entity';
import { UsersRepository } from './users.repository';

@Injectable()
export class UsersService {
  constructor(private readonly usersRepository: UsersRepository) {}

  async createUser(firstName: string, lastName: string): Promise<User> {
    return this.usersRepository.createUser(firstName, lastName);
  }
}

users.module.ts

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersController } from './users.controller';
import { UsersRepository } from './users.repository';
import { UsersService } from './users.service';
import { User } from './user.entity';
import { UsersRepository } from './database/repository/UsersRepository';
@Module({
  imports: [TypeOrmModule.forFeature([User])],
  controllers: [UsersController],
  providers: [UsersRepository, UsersService],
  exports: [UsersService],
})
export class UsersModule {}

Answer №3

// task.entity.ts
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Task {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column()
  title: string;

  @Column()
  description: string;
}
// tasks.repository.ts
import { Injectable } from '@nestjs/common';
import { DataSource, Repository } from 'typeorm';
import { Task } from './tast.entity';

@Injectable()
export class TasksRepository extends Repository<Task> {
  constructor(dataSource: DataSource) {
    super(Task, dataSource.createEntityManager());
  }
}
//tasks.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { TasksController } from './tasks.controller';
import { TasksService } from './tasks.service';
import { Task } from './tast.entity';

@Module({
  imports: [TypeOrmModule.forFeature([Task])], // IMPORTANT: ATTENTION HERE
  controllers: [TasksController], // IMPORTANT: ATTENTION HERE
  providers: [TasksService],
})
export class TasksModule {}
@Injectable()
export class TasksService {
  constructor(
    @InjectRepository(Task) // IMPORTANT: ATTENTION HERE
    private readonly tasksRepository: TasksRepository,
  ) {}

  async getTaskById(id: string): Promise<Task> {
    const task = this.tasksRepository.findOne({
      where: {
        id,
      },
    });

    if (!task) {
      throw new NotFoundException(`Task with id ${id} not found`);
    }

    return task;
  }
}

Answer №4

I recently created a solution to address this issue, feel free to take a look.

nestjs-typeorm-custom-repository

// custom-repository.ts
import { Repository } from 'typeorm';
import { EntityRepository } from 'nestjs-typeorm-custom-repository';

import { CustomEntity } from './custom.entity';

@EntityRepository(CustomEntity)
export class CustomRepository extends Repository<CustomEntity> {}
// custom-service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { CustomEntity } from './custom.entity';
import { CustomRepository } from './custom.repository';

@Injectable()
export class CustomService {
  constructor(
    private readonly customRepository: CustomRepository,
  ) {}
  ...
  }
}
// custom-module.ts
import { Module } from '@nestjs/common';
import { CustomRepositoryModule } from 'nestjs-typeorm-custom-repository';

import { CustomController } from './custom.controller';
import { CustomRepository } from './custom.repository';
import { CustomService } from './custom.service';

@Module({
  imports: [CustomRepositoryModule.forFeature([CustomRepository])],
  controllers: [CustomController],
  providers: [CustomService],
  exports: [CustomService],
})
export class CustomModule {}

Answer №5

The Repository.extend function should be used instead of @deprecated for creating a custom repository

It is recommended to follow this structure:

export const UserRepository = dataSource.getRepository(User).extend({
    findByName(firstName: string, lastName: string) {
        return this.createQueryBuilder("user")
            .where("user.firstName = :firstName", { firstName })
            .andWhere("user.lastName = :lastName", { lastName })
            .getMany()
    },
})

Answer №6

This particular problem was tackled using the following method, and it seems to be working smoothly.

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { PlaygroundEntity } from 'src/entities/playground.entity';
import { Repository } from 'typeorm';

@Injectable()
export class PlaygroundRepository {
  constructor(
    @InjectRepository(PlaygroundEntity)
    private readonly _repository: Repository<PlaygroundEntity>,
  ) {}

  public get _() {
    return this._repository;
  }

  async findWithCriteria() {
    return this._.find({ where: { datecolumn: new Date() } });
  }
}

To utilize it, simply inject it as a regular provider.

import { Inject, Injectable } from '@nestjs/common';
import { PlaygroundRepository } from './repositories/pg.repository';

@Injectable()
export class AppService {
  constructor(
    @Inject(PlaygroundRepository)
    private pgRepository: PlaygroundRepository,
  ) {}

  async start() {
    const customMethod = await this.pgRepository.findWithCriteria();

    const defaultMethod = await this.pgRepository._.find();
  }
}

The implementation appears to be neat and easy to understand.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Issue with Angular 5 HttpClient - PUT request not working in Firefox, however functions correctly in Chrome

Currently in the process of developing a website with Angular 5 and CouchDB. One of my methods in database.service.ts looks like this: import {HttpClient} from '@angular/common/http'; const auth = my database adress; constructor(private http: Ht ...

Using Meteor package to efficiently import JSON arrays into mongoDB

I am currently working on developing a meteor package that will allow users to import JSON files into collections in a mongoDB. However, I'm uncertain about the feasibility of this task. The idea is for the user to upload a JSON file and specify the ...

Exploring the Applications of Directives in Multiple Modules within Angular 2

When I declare the directive in two modules, I get an error that says Type PermissionDirective is part of the declarations of 2 modules. However, when I declare it in only one module, I receive an error stating Can't bind to 'isPermission' s ...

What is the Mongoose counterpart to the ObjectId in MongoDB?

Just to clarify, our system is currently functioning using mongoose exclusively. However, I did have to import {ObjectId} from mongodb in order for it to recognize the ID of the Candidate. Here's a brief overview: we create Candidates (in the Candida ...

Angular - Set value on formArrayName

I'm currently working on a form that contains an array of strings. Every time I try to add a new element to the list, I encounter an issue when using setValue to set values in the array. The following error is displayed: <button (click)="addNewCom ...

What is the best way to incorporate additional properties while utilizing useSession in Next.js with TypeScript?

I've encountered an issue while trying to add new properties using useSession() in a TypeScript environment. Although it works on console.log, there is an error related to the type mismatch. The useSession() function typically returns name, email, an ...

Assistance for Angular 2 Style Guide within Atom: Feature Needed!

My manager uses Atom with a set of eight plugins specifically designed for Angular development. I have the same plugins installed on my system, but I seem to be missing a feature that he has. We're unsure which plugin or preference setting is required ...

Nextjs REACT integration for self-service registration through OKTA

Attempting to integrate the Okta SSR feature for user sign-up in my app has been challenging as I keep encountering this error message: {"errorCode":"E0000060","errorSummary":"Unsupported operation.","errorLink& ...

How is it that TypeScript still expects a valid return from an overridden method, even when it cannot be reached?

After creating a generic RESTClient with some abstract functions, I encountered an issue where not all REST actions are implemented. In these cases, I wanted to throw an error. The problem arose when trying to override the TypeScript method to both return ...

What is the method for launching Chrome synchronously in Selenium WebDriver using createSession()?

After executing the code below using Selenium WebDriver to launch a Chrome browser: import { Driver } from 'selenium-webdriver/chrome'; Driver.createSession(); console.log("I've launched!"); I'm encountering an issue where "I've ...

Issue with calling a function to change the CSS color class of a button in Angular

Within my Angular code, I am attempting to set a CSS color for my button by calling a TypeScript function that will return the appropriate CSS class name. This is the code I have tried: <button style="height: 10%" class="getColor(days.date)">{{days ...

Ways to modify the CSS of an active class within a child component when clicking on another shared component in angular

In my HTML template, I am encountering an issue with two common components. When I click on the app-header link, its active class is applied. However, when I proceed to click on the side navbar's link, its active class also gets applied. I want to en ...

The Google APIs sheet API is throwing an error message stating "Invalid grant: account not found"

I need to retrieve data from a spreadsheet using the Sheet API. After setting up a project in Google Cloud Platform and creating a service account, I granted the account permission to edit the spreadsheet. I then downloaded the credentials in JSON format. ...

In Typescript, a computed property name within a type literal must be associated with an expression that has a type of literal or a 'unique symbol' type.ts(1170)

I'm facing an issue with dynamically generating grid columns using the react-data-table-component library. Here is a sample code snippet showing how to statically define the columns: const columns = [ { name: 'Title', selector: (row: ...

How can I configure React Router V6 to include multiple :id parameters in a Route path, with some being optional?

Currently, I am utilizing react-router@6 and have a Route that was previously used in V5. The route is for vehicles and always requires one parameter (:id = vehicle id), but it also has an optional second parameter (:date = string in DD-MM-YYYY format): &l ...

Error in ReactJS VSCode while integrating Cypress testing: The name 'cy' cannot be found

Currently working on a ReactJS Typescript project using NPM and VSCode. Despite all my Cypress tests running smoothly, I am encountering a syntax error in VSCode: Error: Cannot find name 'cy' Any ideas on how to resolve this issue? https://i.ss ...

How to specify in TypeScript that if one key is present, another key must also be present, without redundantly reproducing the entire structure

In my code, I have a custom type defined like this (but it's not working): type MyType = | { foo: string; } | { foo: string; barPre: string; barPost: string; } | { foo: string; quxPre: string; qu ...

Stop MatDialog instance from destroying

In my application, I have a button that triggers the opening of a component in MatDialog. This component makes API calls and is destroyed when the MatDialog is closed. However, each time I open the MatDialog for the second time by clicking the button agai ...

Version 4.0 of Electron-React-Boilerplate has encountered an error: The property 'electron' is not recognized on the type 'Window & typeof globalThis'. Perhaps you meant to use 'Electron' instead?

I encountered an error while attempting to call the IPCrenderer using the built-in context bridge. The error message reads as follows: Property 'electron' does not exist on type 'Window & typeof globalThis'. Did you mean 'Elect ...

Using TypeScript and controllerAs with $rootScope

I am currently developing an application using Angular 1 and Typescript. Here is the code snippet for my Login Controller: module TheHub { /** * Controller for the login page. */ export class LoginController { static $inject = [ ...