Использование QueryBuilder TypeORM в NestJS

NestJS — это относительно новая веб-инфраструктура JavaScript, которая позволяет создавать серверные приложения корпоративного уровня. Он мощный, применяет лучшие практики и использует новейшие функции JavaScript. Он также имеет поддержку по умолчанию для TypeScript и использует TypeORM , мощную библиотеку управления объектными отношениями, созданную с помощью TypeScript.

TypeORM является одной из самых популярных ORM Node.js. Помимо того, что TypeORM не зависит от базы данных, он имеет уникальный API, который позволяет вам получать доступ к данным в базах данных SQL, таких как MYSQL и PostgreSQL, и базах данных NoSQL, таких как MongoDB, различными способами, используя шаблоны Active Record и Data Mapper.

В этой части мы узнаем, как интегрировать TypeORM с NestJS, добавить драйвер базы данных и выполнять базовые запросы с помощью TypeORM QueryBuilder в NestJS.

Настроить NestJS просто, и есть несколько способов сделать это, в зависимости от ваших потребностей. Однако в этой статье мы установим его с помощью CLI.

npx @nestjs/cli@9.0.0 new nest-app

Эта команда создаст шаблон NestJS «Hello, World!» шаблонное приложение, так что вы можете сразу погрузиться и начать программировать.

Теперь, когда у нас установлен NestJS, давайте интегрируем TypeORM в наше приложение. Обратите особое внимание на этот раздел; вам нужно будет правильно интегрировать TypeORM, прежде чем вы сможете начать писать свои запросы.

Запустите эту команду на своем терминале, чтобы установить драйвер TypeORM и SQLite3 — в этом руководстве мы будем использовать SQLite, чтобы упростить установку и настройку баз данных MySQL или PostgreSQL.

npm install @nestjs/typeorm sqlite3

Создать базовую настройку приложения

Далее давайте создадим контроллеры и службы для скелета нашего приложения запросов, используя интерфейс командной строки NestJS. Выполните следующую команду, чтобы сгенерировать его, и выберите транспортный уровень REST API.

npx @nestjs/cli@9.0.0 g resource posts

Базовая настройка приложения для NestJS и TypeORM

Он спросит, хотите ли вы создать точки входа CRUD. Выберите Да . Создание всего за вас займет некоторое время, но это один из полезных аспектов NestJS.

Теперь ваша структура каталогов должна выглядеть так:

.
├── README.md
├── dist
├── nest-cli.json
├── package-lock.json
├── package.json
├── src
│   ├── app.controller.spec.ts
│   ├── app.controller.ts
│   ├── app.module.ts
│   ├── app.service.ts
│   ├── main.ts
│   └── posts
│       ├── dto
│       │   ├── create-post.dto.ts
│       │   └── update-post.dto.ts
│       ├── entities
│       │   └── post.entity.ts
│       ├── posts.controller.spec.ts
│       ├── posts.controller.ts
│       ├── posts.module.ts
│       ├── posts.service.spec.ts
│       └── posts.service.ts
├── test
├── tsconfig.build.json
├── tsconfig.json
└── yarn.lock

Настройка TypeORM с SQLite

Теперь давайте настроим TypeORM в файле.src/app.module.ts

Изначально это будет выглядеть так:

// src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { PostsModule } from './posts/posts.module';
@Module({
  imports: [PostsModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Импортируйте параметры подключения SQLite, модуль TypeORM и сущность сообщения, как показано в приведенном ниже коде:

// src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { Post } from './posts/entities/post.entity';
import { PostsModule } from './posts/posts.module';

const config: SqliteConnectionOptions = {
  type: "sqlite",
  database: "../db",
  entities: [Post],
  synchronize: true
}

@Module({
  imports: [PostsModule, TypeOrmModule.forRoot(config)],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Теперь давайте поработаем с обновлением, которое мы только что добавили. Во-первых, глядя на форму объекта конфигурации, мы добавили базу данных и сущности и синхронизировали базу данных. Однако важно помнить, что синхронизацию базы данных не следует выполнять в производственной среде, так как это может привести к потере данных.

// the config
const config: SqliteConnectionOptions = {
  type: "sqlite",
  database: "../db",
  entities: [Post],
  synchronize: true // set to false on production 
}

Поскольку мы используем базу данных SQLite, мы можем быстро добавить путь к базе данных с расширением . Если он не существует, он будет автоматически создан для вас. Для MySQL или PostgreSQL формы отличаются, поэтому ознакомьтесь с документацией , чтобы узнать больше."../db",

Создание объектов базы данных

Сущности — это модели базы данных, и в нашем случае наша Post сущность имеет идентификатор и заголовок, как показано в коде ниже:

// src/post/entities/post.entity.ts
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
@Entity()
export class Post {
    @PrimaryGeneratedColumn()
    id: number;
    @Column()
    title: string;
}

Наконец, в общей настройке приложения TypeORM давайте подключим конфигурацию TypeORM с помощью NestJS. Обратите внимание на этот метод, потому что вы можете увидеть нечто подобное при настройке конфигурации на уровне функций..forRoot

  imports: [PostsModule, TypeOrmModule.forRoot(config)],

Здесь у нас есть TypeORM, подключенный к NestJS. Далее давайте интегрируем нашу Post функцию с ORM.

Перейдите к файлу и обновите его со следующей конфигурацией:src/posts/posts.module.ts

// src/posts/posts.module.ts
import { Module } from '@nestjs/common';
import { PostsService } from './posts.service';
import { PostsController } from './posts.controller';
@Module({
  controllers: [PostsController],
  providers: [PostsService]
})
export class PostsModule {}

Затем импортируйте объект TypeORM Postи обновите код, установив значение импорта модуля с помощью . Обратите внимание, что мы используем и передаем массив сущностей, а не конфигурацию модуля на уровне приложения.[TypeOrmModule.forFeature([Post])]forFeature

import { Module } from '@nestjs/common';
import { PostsService } from './posts.service';
import { PostsController } from './posts.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Post } from './entities/post.entity';
@Module({
  imports: [TypeOrmModule.forFeature([Post])],
  controllers: [PostsController],
  providers: [PostsService]
})
export class PostsModule {} 

Выполнение базовых запросов с помощью TypeORM QueryBuilder в NestJS

Существует несколько способов доступа к базе данных с помощью TypeORM и NestJS, включая использование API-интерфейса репозитория, API-интерфейса Entity Manager и API-интерфейса DataSource.

Ниже приведен краткий пример того, как получить элемент по идентификатору с помощью упомянутых выше API.

//src/posts/posts.service.ts

@Injectable()
export class PostsService {

  constructor(
    @InjectRepository(Post) private postRepository: Repository<Post>,
    @InjectEntityManager() private postManager: EntityManager,
    @InjectDataSource() private dataSource: DataSource
  ) { }

  async findOne(id: number) {

    const postWithRepository = await this.postRepository.findOneBy({ id });

    const postWithRepositoryQueryBuilder = await this.postRepository
      .createQueryBuilder("post")
      .where("post.id= :postId", { postId: id })
      .getOne()

    const postWithEntityManager = await this.postManager
      .createQueryBuilder(Post, "post")
      .where("post.id= :postId", { postId: id })
      .getOne()

    const postWithDataSource = await this.dataSource
      .createQueryBuilder()
      .select("post")
      .from(Post, "post")
      .where("post.id= :postId", { postId: id })
      .getOne()

    return {
      postWithRepository,
      postWithRepositoryQueryBuilder,
      postWithEntityManager,
      postWithDataSource
    };
  }

}

Как видите, мы инициализировали слои доступа к данным в конструкторе, а затем использовали их внутри методов.

//src/posts/posts.service.ts
...
constructor(
    @InjectRepository(Post) private postRepository: Repository<Post>,
  ) { }
...

Помимо этого, остальная часть кода — это обычные запросы TypeORM; вы можете глубже изучить TypeORM, ознакомившись с их документацией .

Все запросы в приведенном выше коде вернут один и тот же результат. Итак, есть несколько способов достижения одной цели, но какой из них самый эффективный?

При работе с небольшими наборами данных оба метода работают одинаково. Однако API QueryBuilder более эффективен, чем запросы репозитория, при работе с большими наборами данных с несколькими отношениями. Возможно, это связано с тем, что API QueryBuilder относительно ближе к необработанным SQL-запросам, чем API репозитория.

Использование JOIN запросов в TypeORM с NestJS

Если вы написали SQL-запрос, который включает доступ к данным из нескольких таблиц с помощью SQL, скорее всего, вы писали запрос JOIN раньше. JOIN запросы позволяют запрашивать данные из нескольких таблиц одновременно. Давайте посмотрим на LEFT JOIN операцию в TypeORM. Этот тип запроса вернет все строки из левой таблицы и соответствующие строки из правой таблицы.

Есть несколько способов выполнить LEFT JOIN операцию в TypeORM. Рассмотрим некоторые из них:

  • Использование find опций
  • Использование QueryBuilder

Несмотря на то, что мы сосредоточены на использовании QueryBuilder, мы покажем вам пример, в котором используются оба параметра, чтобы вы могли увидеть различия между ними.

Давайте посмотрим на эту LEFT JOIN операцию SQL ниже. Мы преобразуем его, чтобы использовать find опцию и метод QueryBuilder TypeORM.

SELECT * FROM "user"
LEFT JOIN "courses" "course" ON "course"."id" = "user"."courseId"
WHERE "course"."name" = 'JavaScript Fundamentals' AND "course"."length" = '8 hours'

Использование find в TypeORM

userRepository.find({
    relations: { course: true },
    where: {
        course: { name: "JavaScript Fundamentals", length: "8 hours" },
    },
})

Приведенный выше код представляет собой LEFT JOIN запрос с использованием параметра TypeORM find. К счастью, это просто, потому что TypeORM определяет JOIN для вас лучшее и дает вам соответствующий результат. Теперь давайте реализуем вышеуказанный запрос с помощью TypeORM QueryBuilder.

Использование QueryBuilder для JOIN

const user = this.userManager
      .createQueryBuilder(User, "user")
      .leftJoin("course"."id", "course")
      .where("course.name = :name", { name: "JavaScript Fundamentals" })
      .andWhere("course.length = :length", { length: "8 hours" })

При использовании QueryBuilder у вас также есть опции для разных типов JOINS, в отличие от find опции, которая делает все «под капотом».

Вот некоторые дополнительные JOINS, доступные в TypeORM.

Заключение

Если вы хотите быстро создать надежную серверную часть без ущерба для качества, рассмотрите возможность использования с TypeORM. В этой статье мы узнали, как интегрировать и использовать TypeORM QueryBuilder с NestJS, но мы только коснулись того, что вы можете делать с TypeORM. Ознакомьтесь с документацией по TypeORM, чтобы узнать больше .