В этом посте мы создадим Rest API, настроив интеграцию NestJS TypeORM с базой данных MySQL.
NestJS хорошо работает с базами данных разных типов. Другими словами, это позволяет нам интегрироваться с любой базой данных, будь то SQL (например, MySQL) или No-SQL (например, MongoDB). Доступно несколько вариантов в зависимости от ваших предпочтений и варианта использования.
Оглавление
- Установка пакета NestJS TypeORM
- Конфигурация NestJS TypeORM
- Сущность и репозиторий
- Создание модуля
- Создание контроллера
- Заключение
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, сообщите нам об этом в разделе комментариев ниже!