DevGang
Авторизоваться

Как настроить источник данных TypeORM в проекте NestJS

Привет! С тех пор как я начал работать с NestJS, я искал надежный способ управления базой данных с помощью TypeORM. Сегодня я расскажу о своем пути и шагах, которые я предпринял, чтобы всё это настроить.

Итак, прежде чем мы погрузимся в работу, давайте попробуем понять, что такое TypeORM и NestJS.

Что такое TypeORM?

TypeORM – это инструмент объектно-реляционного отображения (ORM), который упрощает работу с базами данных в приложениях на Node.js и TypeScript. Он поддерживает различные базы данных, такие как MySQL, PostgreSQL, SQLite и другие, позволяя разработчикам использовать концепции объектно-ориентированного программирования вместо того, чтобы работать с низкоуровневыми SQL-запросами.

TypeORM также предоставляет такие возможности, как миграция схем, построение запросов и управление связями между таблицами.

Что такое NestJS?

NestJS – это прогрессивный фреймворк Node.js, предназначенный для создания эффективных, надежных и масштабируемых приложений на стороне сервера. Он использует возможности TypeScript, позволяя разработчикам писать структурированный, удобный в обслуживании код.

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

Кроме того, NestJS легко интегрируется с другими библиотеками и фреймворками, включая TypeORM, что позволяет оптимизировать рабочие процессы разработки. По умолчанию он использует надежный фреймворк HTTP-сервера Express, а также может быть настроен на использование других фреймворков HTTP-сервера Node.js.

Хорошо, это уже много, верно? Что ж, прежде чем двигаться дальше, давайте попробуем разобрать фразу «NestJS - это прогрессивный фреймворк Node.js»‎, которая просто означает, что NestJS использует новейшие возможности языка JavaScript и серверных фреймворков, тем самым обеспечивая разработчикам гибкость в написании кода на наиболее подходящем языке для их проектов.

Предварительные требования к реализации этого руководства

  • Node.js. Минимум версия 18
  • npm. Минимум версия 8
  • Postgresql
  • Базовая ознакомленность с Typescirpt и NestJS
  • Pgadmin 4

Как настроить проект NestJS

Выполните следующие команды, чтобы установить ваш проект NestJS:

npm i -g @nestjs/cli # install nestj cli globally
nest new simple-crm # start a new nestjs project

После установки запустите сервер разработки:

npm run start:dev # start the app in watch mode

Теперь давайте протестируем наш проект, чтобы убедиться, что nest-cli правильно настроил весь кодовый код, отправив запрос get на корневой URL.

init_test
init_test

Отлично! Наш проект запущен и работает.

Как настроить источник данных TypeORM для обеспечения постоянства данных

npm install --save @nestjs/typeorm typeorm # nestjs typeorm drivers
npm install --save pg # typeorm postgressql driver

Давайте создадим базу данных для проекта из интерфейса Pgadmin 4.

Откройте интерфейс Pgadmin 4 и щелкните правой кнопкой мыши на вкладке Databases, чтобы создать новую базу данных, как показано ниже.

Убедитесь, что база данных успешно создана.

Отлично, пришло время добавить базу данных в наше приложение NestJS с помощью TypeORM.

Создайте новую папку datasource в папке src/ вашего приложения, как показано ниже.

confirm_folder
confirm_folder

Создайте новый файл typeorm.module.ts в папке datasource и добавьте в него следующий код:

import { DataSource } from 'typeorm';
import { Global, Module } from '@nestjs/common';

@Global() // makes the module available globally for other modules once imported in the app modules
@Module({
  imports: [],
  providers: [
    {
      provide: DataSource, // add the datasource as a provider
      inject: [],
      useFactory: async () => {
        // using the factory function to create the datasource instance
        try {
          const dataSource = new DataSource({
            type: 'postgres',
            host: 'localhost',
            port: 5432,
            username: 'ayo',
            password: 'haywon',
            database: 'simple-crm_db',
            synchronize: true,
            entities: [`${__dirname}/../**/**.entity{.ts,.js}`], // this will automatically load all entity file in the src folder
          });
          await dataSource.initialize(); // initialize the data source
          console.log('Database connected successfully');
          return dataSource;
        } catch (error) {
          console.log('Error connecting to database');
          throw error;
        }
      },
    },
  ],
  exports: [DataSource],
})
export class TypeOrmModule {}

Добавьте модуль TypeORM в массив импортов модулей App, как показано ниже:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from './datasource/typeorm.module';

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

Затем сохраните и подтвердите в консоли успешное подключение к базе данных.

Если вы видите, что база данных успешно подключена, то вы молодцы! В противном случае вернитесь к предыдущим шагам, чтобы проверить, правильно ли вы следовали настройкам.

Теперь мы можем продолжить использовать наш сервис datasource с помощью TypeORM.

Давайте создадим модуль users, контроллер, провайдер и структуру для взаимодействия с нашей только что подключенной базой данных.

nest g module users && nest g service users && nest g controller users

Приведенная выше команда сгенерирует модуль users, сервис и контроллер и обновит app.module.ts с модулем users.

Добавьте следующий код в файл users.entity.ts и перезапустите сервер разработки, чтобы создать таблицу пользователей в базе данных.

import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';

@Entity('user')
export class UserEntity {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ unique: true })
  username: string;

  @Column()
  password: string;
}

Проверьте интерфейс Pgadmin 4 и убедитесь, что TypeORM автоматически загрузил UserEntity и создал таблицу user в вашей базе данных, как показано ниже.

Возможно, вам понадобится обновить базу данных, если вы не увидите её в первый раз.

Теперь давайте реализуем наш первый обработчик службы users, добавьте следующий код в файл users.service.ts:

import {
  HttpException,
  HttpStatus,
  Injectable,
  InternalServerErrorException,
  Logger,
} from '@nestjs/common';
import { DataSource } from 'typeorm';
import { UserEntity } from './users.entity';

export interface CreateUser {
  username: string;
  password: string;
}

@Injectable()
export class UsersService {
  private userRepository;
  private logger = new Logger();
  //   inject the Datasource provider
  constructor(private dataSource: DataSource) {
    // get users table repository to interact with the database
    this.userRepository = this.dataSource.getRepository(UserEntity);
  }
  //  create handler to create new user and save to the database
  async createUser(createUser: CreateUser): Promise<UserEntity> {
    try {
      const user = await this.userRepository.create(createUser);
      return await this.userRepository.save(user);
    } catch (err) {
      if (err.code == 23505) {
        this.logger.error(err.message, err.stack);
        throw new HttpException('Username already exists', HttpStatus.CONFLICT);
      }
      this.logger.error(err.message, err.stack);
      throw new InternalServerErrorException(
        'Something went wrong, Try again!',
      );
    }
  }
}

Мы добавили метод createUser для обработки создания пользователя, когда POST-запрос отправляется с требуемым телом запроса в контроллер конечной точки, использующий метод службы createUser.

В качестве аргумента функция принимает объект createUser с типом интерфейса CreateUser. Обычно это должен быть объект DTO (Data Transfer Object) для структуры и проверки типов данных, но поскольку это выходит за рамки данного руководства, мы используем интерфейс только для формы данных.

Мы вызвали метод create хранилища userRepository и присвоили его возврат переменной user для хранения только что созданного объекта user. Затем мы вызвали метод save, чтобы сохранить объект в базе данных.

Теперь давайте воспользуемся обработчиком сервиса createUser в контроллере users, который обрабатывает POST-запрос на создание нового пользователя.

import { Body, Controller, Post } from '@nestjs/common';
import { CreateUser, UsersService } from './users.service';

@Controller('users')
export class UsersController {
  constructor(private userService: UsersService) {}

  @Post('/create')
  //   handles the post request to /users/create endpoint to create new user
  async signUp(@Body() user: CreateUser) {
    return await this.userService.createUser(user);
  }
}

Протестируйте только что созданную конечную точку, отправив POST-запрос на http://localhost:3000/users/create с именем пользователя и паролем в качестве тела запроса.

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

Расширение репозитория DataSource для пользовательских методов

Если вы хотите оптимизировать запросы к базе данных, внедрить новые операции манипулирования данными или интегрироваться со сторонними сервисами, расширение репозитория DataSource с помощью пользовательских методов может стать решающим фактором для беспрепятственного взаимодействия с базой данных.

Здесь мы рассмотрим преимущества пользовательских методов и предоставим пошаговое руководство по их внедрению в ваши приложения NestJS. Итак, давайте погрузимся в работу и раскроем весь потенциал репозитория DataSource!

Вот некоторые из основных преимуществ пользовательских методов репозитория:

Индивидуальная функциональность: Пользовательские методы позволяют разработчикам внедрять специфические функциональные возможности, которые недоступны в стандартном хранилище DataSource. Настраивая репозиторий DataSource с помощью пользовательских методов, разработчики могут решать уникальные задачи, выполнять операции манипулирования данными, агрегировать их или оптимизировать, что необходимо для их проекта.

Оптимизированная производительность: Пользовательские методы могут быть разработаны для оптимизации запросов к базе данных, получения данных и операций манипулирования данными, что приводит к повышению производительности и эффективности. Используя пользовательские методы, разработчики могут реализовать оптимизированные алгоритмы, механизмы кэширования или оптимизацию запросов с учетом конкретных потребностей и характеристик своих приложений.

Улучшенная переиспользуемость и ремонтопригодность кода: Пользовательские методы способствуют повторному использованию кода, инкапсулируя конкретную логику, алгоритмы или операции в компоненты многократного использования. Модулируя пользовательские методы, разработчики могут поддерживать более чистые, организованные и удобные кодовые базы, что облегчает управление, отладку и совершенствование репозитория DataSource в долгосрочной перспективе.

Ну вот и все, переходим к действию. Итак, у нас есть простое CRM-приложение, связанное с управлением пользователями. Давайте добавим пользовательский метод репозитория, который поможет нам фильтровать пользователей по имени пользователя.

Чтобы реализовать это, создадим модуль datasource и службу datasource. Мы создадим эти файлы в соответствии с принципами модульности архитектурного паттерна NestJS.

Создайте файлы в ранее созданной папке datasource и добавьте следующий код:

// datasource.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from './typeorm.module';
import { DataSourceService } from './datasource.service';

@Module({
  imports: [TypeOrmModule],
  providers: [DataSourceService],
  exports: [DataSourceService],
})
export class DataSourceModule {}
// datasource.service.ts
import { Injectable } from '@nestjs/common';
import { UserEntity } from 'src/users/users.entity';
import { DataSource } from 'typeorm';

export interface UsernameQuery {
  username: string;
}

@Injectable()
export class DataSourceService {
  constructor(private dataSource: DataSource) {}

  //   extend userRepository to add custom methods
  userCustomRepository = this.dataSource.getRepository(UserEntity).extend({
    async filterUser(usernameQuery: UsernameQuery): Promise<UserEntity[]> {
      const { username } = usernameQuery;
      console.log(username);
      // initialize a query builder for the userrepository
      const query = this.createQueryBuilder('user');
      //   filter user where username is like the passed username
      query.where('(LOWER(user.username) LIKE LOWER(:username))', {
        username: `%${username}%`,
      });
      return await query.getMany();
    },
  });
}

Из приведенного выше кода datasource.service.ts мы расширили UserRepository, вызвав метод getRepository на сервисе dataSource и передав UserEntity в качестве аргумента, чтобы получить хранилище для конкретной таблицы.

Затем мы вызвали метод extend на userRepository, полученный в результате getRepository, чтобы добавить наш пользовательский метод. В наш метод extend мы передали объект, который будет содержать все наши пользовательские методы для пользовательского хранилища, которое мы определили как userCustomRepository. Здесь мы просто добавили только один пользовательский метод в наше пользовательское хранилище, а именно filterUser. Он запускает запрос фильтрации таблицы пользователей по указанному имени пользователя.

Поскольку наш DataSourseService является инжектируемым, мы можем внедрить его в наш UserService и использовать только что созданный метод filterUser после добавления модуля DataSourceModule в массив imports модуля user следующим образом.

// users.module.ts
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { DataSourceModule } from 'src/datasource/datasource.module';

@Module({
  imports: [DataSourceModule], // add the DataSourceModule to the import array 
  providers: [UsersService],
  controllers: [UsersController],
})
export class UsersModule {}

Давайте используем метод filter из CustomUserRepository в нашем UserService для фильтрации пользователей по любому имени пользователя, переданному в качестве аргумента запроса при отправке запроса.

// users.service.ts
import {
  HttpException,
  HttpStatus,
  Injectable,
  InternalServerErrorException,
  Logger,
} from '@nestjs/common';
import { DataSource } from 'typeorm';
import { UserEntity } from './users.entity';
import {
  DataSourceService,
  UsernameQuery,
} from 'src/datasource/datasource.service';

export interface CreateUser {
  username: string;
  password: string;
}

@Injectable()
export class UsersService {
  private userRepository;
  private customUserRepository;
  private logger = new Logger();
  //   inject the Datasource provider
  constructor(
    private dataSource: DataSource,
    private dataSourceService: DataSourceService, // inject our datasource service
  ) {
    // get users table repository to interact with the database
    this.userRepository = this.dataSource.getRepository(UserEntity);
    // assigning the dataSourceService userCustomRepository to the class customUserRepository
    this.customUserRepository = this.dataSourceService.userCustomRepository;
  }
  //  create handler to create new user and save to the database
  async createUser(createUser: CreateUser): Promise<UserEntity> {
    try {
      const user = await this.userRepository.create(createUser);
      return await this.userRepository.save(user);
    } catch (err) {
      if (err.code == 23505) {
        this.logger.error(err.message, err.stack);
        throw new HttpException('Username already exists', HttpStatus.CONFLICT);
      }
      this.logger.error(err.message, err.stack);
      throw new InternalServerErrorException(
        'Something went wrong, Try again!',
      );
    }
  }
  // the userService filterByUsername handler
  async filterByUsername(usernameQuery: UsernameQuery): Promise<UserEntity[]> {
    try {
    // calling the customUserRepository filterUser custom method
      return await this.customUserRepository.filterUser(usernameQuery);
    } catch (err) {
      this.logger.error(err.message, err.stack);
      throw new InternalServerErrorException(
        'Something went wrong, Try again!',
      );
    }
  }
}

Из приведенного выше кода мы внедрили наш пользовательский DataSourceService, добавив следующее в конструктор класса private dataSourceService: DataSourceService,.

Сервис filterByUsername обрабатывает запрос, который мы используем в нашем пользовательском методе filterUser await this.customUserRepository.filterUser(usernameQuery);, который возвращает обещание.

Теперь давайте используем этот обработчик сервиса в нашем контроллере пользователя для фильтрации пользователей по их имени пользователя.

// users.controller.ts
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
import { CreateUser, UsersService } from './users.service';
import { UserEntity } from './users.entity';
import { UsernameQuery } from 'src/datasource/datasource.service';

@Controller('users')
export class UsersController {
  constructor(private userService: UsersService) {}

  @Post('/create')
  //   handles the post request to /users/create endpoint to create new user
  async signUp(@Body() user: CreateUser): Promise<UserEntity> {
    return await this.userService.createUser(user);
  }

  @Get('') // get request handler that returns the filtered results of the users table
  async filterUser(
    @Query() usernameQuery: UsernameQuery // extracts the username query param for the endpoint url,
  ): Promise<UserEntity[]> {
    return await this.userService.filterByUsername(usernameQuery);
  }
}

Протестируйте конечную точку фильтрации.

Здесь мы получили список, содержащий один объект user с именем пользователя, похожим на имя пользователя, которое мы передали в качестве запроса.

Заключение

Вуаля! Вот и все, теперь вы готовы приступить к работе с NestJS, TypeORM и DataSource.

Спасибо за чтение!

Если вы нашли эту статью полезной, пожалуйста, поделитесь ею с друзьями и коллегами! Следите за новыми материалами, и давайте продолжать учиться и развиваться вместе.

Источник:

#JavaScript #NestJS
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу