В этом посте мы создадим Rest API, настроив интеграцию NestJS TypeORM с базой данных MySQL.

NestJS хорошо работает с базами данных разных типов. Другими словами, это позволяет нам интегрироваться с любой базой данных, будь то SQL (например, MySQL) или No-SQL (например, MongoDB). Доступно несколько вариантов в зависимости от ваших предпочтений и варианта использования.

Оглавление

  1. Установка пакета NestJS TypeORM
  2. Конфигурация NestJS TypeORM
  3. Сущность и репозиторий
  4. Создание модуля
  5. Создание контроллера
  6. Заключение

1 — Установка пакета NestJS TypeORM

давайте создадим NestJS-проект:

nest new nestjs-products

TypeORM написан на Typescript. Это также одна из самых зрелых сред ORM, доступных для Typescript.

Чтобы упростить интеграцию, Nest предоставляет пакет @nestjs/typeorm. Для начала мы должны сначала установить пакеты, как показано ниже:

npm install --save @nestjs/typeorm typeorm mysql2

На самом деле мы устанавливаем пакет @nestjs/typeorm, также мы устанавливаем пакет mysql2, так как мы пытаемся использовать MySQL для нашего проекта.

С помощью docker hub мы можем получить последнюю версию образа MySQL для сборки MySQL Server, или вы можете использовать свой собственный mysqld, если он уже установлен.

Чтобы создать контейнер nestjs-mysql-c1:

docker run --name nestjs-mysql-c1 -p 3306:3306 -e MYSQL_ROOT_PASSWORD=password -d mysql:latest

2 — Конфигурация NestJS TypeORM

После того, как пакеты установлены и контейнер запущен, нам нужно начать соединять части в нашем приложении.

Первый шаг — настроить соединение с MySQL с помощью TypeORM. Мы можем сделать это, создав: config/typeorm.conf.ts

// typeorm.conf.ts
import { TypeOrmModuleOptions } from '@nestjs/typeorm'
import { ProductEntity } from 'src/products/product.entity/product.entity'

export const typeOrmOptions: TypeOrmModuleOptions = {
    type: 'mysql',
    host: 'localhost',
    port: 3306,
    username: 'root',
    password: '',
    database: 'nestjs-products',
    entities: [ProductEntity],
    synchronize: true,
}

На самом деле мы используем метод forRoot(), доступный как часть TypeOrmModule, для передачи параметров конфигурации. Этими параметрами являются тип базы данных, хост, порт, имя пользователя и пароль базы данных, а также имя базы данных. Другие детали включают учетные данные для подключения.

// app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { typeOrmOptions } from './config/typeorm.conf';
import { ProductsModule } from './products/products.module';

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

Метод forRoot() поддерживает все свойства конфигурации, являющиеся частью функции createConnection(). Обратите внимание на атрибут entity в объекте конфигурации. По сути, это сообщает NestJS о сущностях, которые будут в нашем приложении. Мы создадим объект в следующем разделе.

3 — Сущность и репозиторий

Следующим шагом будет создание репозитория.

По сути, NestJS поддерживает шаблон проектирования службы репозитория.

  • Оберните запросы Eloquent в слой репозитория.
  • Использование сервисного уровня для управления объектами категории и продукта (создание, удаление, получение и т. д.)

Например, мы будем создавать приложение «Товары-Категории». Поэтому мы создадим объект Product, как показано ниже:

// product.entity.ts
import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from "typeorm";

@Entity()
export class ProductEntity extends BaseEntity {
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    name: string;

    @Column()
    description: string;

    @Column()
    price: number;

    @Column({ default: true})
    isAvailable: boolean;

    @Column({ type: "timestamp", default: () => "CURRENT_TIMESTAMP"})
    createdAt: string;

    @Column({ type: "timestamp", default: () => "CURRENT_TIMESTAMP"})
    updatedAt: string;
}

Мы сохраним указанный выше объект Product в папке продукта. По сути, мы хотим, чтобы он был частью модуля продукта. Всегда полезно держать объекты близко к общему домену.

В приведенном выше примере важен декоратор @Entity. По сути, он говорит NestJS зарегистрировать этот класс как сущность и разрешить репозиторию доступ к нему. Кроме того, мы уже добавили объект Product в массив объектов в app.module.ts.

4 — Создание модуля

Модули — отличный способ изолировать функции домена в приложении.

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

// products.module.ts
import { Module } from '@nestjs/common';
import { ProductsService } from './products.service';
import { ProductsController } from './products.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ProductEntity } from './product.entity/product.entity';

@Module({
    imports:[TypeOrmModule.forFeature([ProductEntity])],
    providers:[ProductsService],
    controllers:[ProductsController]
})
export class ProductsModule{}

Этот модуль использует метод forFeature(), чтобы определить, какие репозитории зарегистрированы в текущей области. Нам нужна эта часть конфигурации, чтобы внедрить репозиторий в сервис.

См. ниже код для класса обслуживания

// products.service.ts
import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { ProductEntity } from "./product.entity/product.entity";

@Injectable()
export class ProductsService {
    constructor(
        @InjectRepository(ProductEntity)
        private productRepository: Repository<ProductEntity>
    ){}

    async findAll(): Promise<ProductEntity[]> {
        return await this.productRepository.find();
    }

    async findOne(id: string): Promise<ProductEntity> {
        return await this.productRepository.findOneById(id);
    }

    async createProduct(product: ProductEntity): Promise<ProductEntity> {
        return await this.productRepository.save(product);
    }

    async updateProduct(id: string, productEntity: ProductEntity): Promise<ProductEntity> {
        const product = await this.findOne(id)
        if (product) {
            const {name, description, price, isAvailable} = productEntity

            product.name = name
            product.description = description
            product.price = price
            product.isAvailable = isAvailable
            product.save()

            return product
        }
    }

    async destroyProduct(id: string) {
        const product = await this.findOne(id)
        return await this.productRepository.remove(product)
    }
}

Обратите внимание, что мы используем декоратор @InjectRepository() для внедрения репозитория Product в сервис.

Затем мы используем репозиторий для написания нескольких функций, таких как выборка данных и создание новой записи.

5 — Создание контроллера

Контроллер NestJS — это, по сути, набор обработчиков запросов для обработки входящих запросов.

Для нашего примера приложения мы просто создаем контроллер, как показано ниже:

// products.controller.ts

import { Body, Controller, Delete, Get, HttpStatus, Param, Post, Put, Res } from "@nestjs/common";
import { ProductEntity } from "./product.entity/product.entity";
import { ProductsService } from "./products.service";

@Controller('products')
export class ProductsController {
    constructor(private readonly productsService: ProductsService){}

    @Get()
    async fetchAll(@Res() response) {
        const products = await this.productsService.findAll();
        return response.status(HttpStatus.OK).json({
            products
        })
    }

    @Post()
    async createProduct(@Res() response, @Body()productEntity: ProductEntity) {
        const product = await this.productsService.createProduct(productEntity);
        return response.status(HttpStatus.CREATED).json({
            product
        })
    }

    @Get('/:id')
    async findById(@Res() response, @Param('id') id) {
        const product = await this.productsService.findOne(id);
        if (product) {
            return response.status(HttpStatus.OK).json({
                product
            })   
        } else {
            return response.status(HttpStatus.NOT_FOUND).json({
                "message": "not found"
            })
        }
    }

    @Put('/:id')
    async updateProduct(@Res() response, @Param('id') id, @Body()productEntity: ProductEntity) {
        const product = await this.productsService.updateProduct(id, productEntity)
        if (product) {
            return response.status(HttpStatus.CREATED).json({
                product
            })
        } else {
            return response.status(HttpStatus.INTERNAL_SERVER_ERROR).json({
                "message": "something wrong !"
            })
        }
    }

    @Delete('/:id')
    async destroyProduct(@Res() response, @Param('id') id) {
        if (await this.productsService.destroyProduct(id)) {
            return response.status(HttpStatus.OK).json({
                "message": "Product Deleted"
            })
        } else {
            return response.status(HttpStatus.INTERNAL_SERVER_ERROR).json({
                "message": "something wrong !"
            })
        }
    }
}

По сути, мы используем ProductsService для выполнения операций чтения и записи в репозитории Product.

Если вы запустите приложение сейчас, вы сможете отправить запросы на создание новой книги или книг, а затем получить к ним доступ с помощью конечной точки /products. Все, что вам нужно сделать, это использовать соответствующий метод, то есть POST или GET.

{
    "products": [
        {
            "id": 1,
            "name": "lorem ipsum",
            "description": "lorem ipsum dolor sit ament dev",
            "price": 19,
            "isAvailable": false,
            "createdAt": "2022-12-05T20:55:43.000Z",
            "updatedAt": "2022-12-05T20:55:43.000Z"
        }
    ]
}

Заключение

Благодаря этому мы успешно создали наш API NestJS TypeORM с базой данных MySQL. Мы рассмотрели различные варианты конфигурации, а также использовали шаблон сервис-репозиторий для создания объекта и использования его для выполнения различных операций CRUD.

Код этого проекта доступен на Github.

Если у вас есть какие-либо идеи или у вас есть предпочтительный способ создания NestJS API, сообщите нам об этом в разделе комментариев ниже!