天天看點

Nest.js 快速入門:實作對 Mysql 單表的 CRUD

Nest.js 是一個 Node.js 的後端開發架構,它實作了 MVC 模式,也支援了 IOC(自動注入依賴),比 Express 這類處理請求響應的庫高了一個層次。而且也很容易內建 GraphQL、WebSocket 等功能,适合用來做大規模企業級開發。

Nest.js 在國内外用的都挺多的,今天我們就來入門下吧:做一個筆記管理功能,實作對 mysql 單表的增删改查并提供 Restful 的接口。

後面要介紹的内容比較多,我們先來看下最終的效果吧:

完整代碼上傳了 github:https://github.com/QuarkGluonPlasma/nestjs-exercize

Nest.js + Typeorm 基礎

mysql 資料庫和 Typeorm

首先從離前端比較遠的資料庫講起。

在 mysql 的官網下載下傳 mysql,安裝并啟動服務。

這時候就可以用指令行來寫 sql 操作資料庫了。

Nest.js 快速入門:實作對 Mysql 單表的 CRUD

但是指令行操作不夠友善,是以我們還要下載下傳一個有界面的 mysql 用戶端,我這裡用的是 navicat。

Nest.js 快速入門:實作對 Mysql 單表的 CRUD

它可以可視化的建立資料庫、表等,可以在編輯器裡寫 sql 然後執行。比如圖中我建立了 hello 的資料庫和一堆表。

Node.js 代碼裡同樣可以連接配接上資料庫服務,然後遠端執行 sql 來對資料庫表做增删改查。

但直接執行 sql 比較繁瑣,能不能我隻操作對象,對象屬性變了就自動去執行 sql 來同步資料庫呢?就像 vue 的資料變了自動同步視圖一樣。

資料庫和對象關系的映射就叫做 ORM(Object Relational Mapping),也就是把表映射成對象,把表與表之間的關聯映射成對象之間的關系。之後對對象的操作會通過 sql 同步到資料庫。

Typeorm 就是一個實作 orm 的架構,可以通過裝飾器來描述映射關系,比如 @Entity(實體)、@Column(列)、@PrimaryGeneratedColumn(主鍵 ID 自動生成)

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

@Entity()
export class Note{

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    title: string;

    @Column()
    content: string;
}
           

複制

通過裝飾器聲明了關系,那麼在建立了資料庫連接配接之後,我們隻需要操作對象,Typeorm 就會自動去執行 sql 來把變動同步到資料庫。

這樣,我們對資料庫的表的操作和增删改查就實作了。

資料庫部分搞定之後,我們再往前看一下處理請求的部分。

http 請求和 Nest.js

處理請求的後端架構我們使用 Nest.js,它提供了 Controller、Service 等劃分,這是對 MVC 模式的實作。

Controller 裡面負責處理請求,把處理過的參數傳遞給 service。

Service 負責業務邏輯的實作,基于 Typeorm 的增删改查功能來實作各種上層業務邏輯。

除此以外,Nest.js 還劃分了 Module,這個 Module 是邏輯上的子產品,和我們常說的檔案對應的子產品不同,它包含了 Controller、Service 等,是對這些資源的邏輯劃分。

Nest.js 快速入門:實作對 Mysql 單表的 CRUD

Module 和 Module 之間還可以有依賴關系,也就有 imports 和 exports。

是以,子產品的聲明就是這樣的:

import { Module } from '@nestjs/common';

@Module({
  imports: [AaaModule],
  controllers: [BbbController],
  providers: [BbbService],
  exports: [BbbService]
})
export class BbbModule {}
           

複制

這裡通過 @Module 的裝飾器來聲明了 Bbb 的子產品,它依賴了 Aaa 子產品,也就是在 imports 引入的 AaaModule。controllers 是控制器,包含 BbbController,providers 是提供商,有 service、factory 等類型,這裡包含 BbbService,同時,還導出了 BbbService 可以被其他子產品引入。

Controller 的聲明也是通過裝飾器:

@Controller()
export class BbbController {
}
           

複制

Service 的聲明也是用裝飾器,隻不過不叫 Service,而叫 Injectable。

@Injectable()
export class BbbService {
}
           

複制

至于為什麼叫 Injectable,就涉及到了 IOC 的概念了。

IOC(Inverse Of Control)是控制反轉的意思,就是隻需要聲明你的依賴,不需要建立依賴的對象,容器會注入給你。

因為所有的對象都是由容器管理的,那麼自然就可以在建立對象的時候注入它需要的依賴,這就是 IOC 的原理。

Service 是可以被作為依賴注入到其他類的執行個體中去的,是以用 Injectable 裝飾器。

Nest.js 快速入門:實作對 Mysql 單表的 CRUD

所有的 Module 會有一個根 Module 作為入口,啟動 IOC 容器就是從這個子產品開始的:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import "reflect-metadata";

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();
           

複制

上面就是典型的 Nest.js 啟動代碼,從 AppModule 這個根 Module 開始建立 IOC 容器,處理從 3000 端口發過來的請求。

reflect-metadata 子產品是用于解析類的裝飾器的,因為要給某個類的執行個體注入依賴就得能解析出它通過裝飾器聲明了哪些依賴,然後注入給它。是以要實作 IOC 需要依賴這個包。

這就是 Nest.js 大概的設計了:IOC + MVC,通過 IOC 容器來管理對象的依賴關系,通過 Controller、Service、Module 來做職責上的劃分。

Nest.js 結合 Typeorm

Typeorm 是做把對象的操作通過 sql 同步為對資料庫操作的 orm 的,而 Nest.js 是做 Web 後端應用的 MVC 分層以及通過 IOC 管理對象的建立和依賴的。這倆很自然的可以結合,結合的方式就是 @nestjs/typeorm 包。

@nestjs/typeorm 包提供了 TypeOrmModule 這個 Module,它有兩個靜态方法 forRoot、forFeature。

forRoot 用于建立資料庫連接配接,傳入一些配置參數,在入口 Module 引入。

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: '你的密碼',
      database: '資料庫名',
      synchronize: true
    }),
    NotesModule
  ]
})
export class AppModule {}
           

複制

forFeature 用于建立不同實體類對應的 Repository,在用到該實體的 Module 裡引入。

@Module({
  imports: [TypeOrmModule.forFeature([Aaa])],
  controllers: [AaaController],
  providers: [AaaService],
  exports: [AaaService]
})
export class AaaModule {}
           

複制

我們知道了 Typeorm 和 Nest.js 都是做什麼的和怎麼用,簡單小結一下:

Typeorm 是 ORM 架構,用于把對象的操作同步為對資料庫的操作,會自動執行 sql 語句。

Nest.js 是 MVC 架構,用于 Web 後端應用的邏輯分層,還提供了 Module 用來進一步劃分 Controller 和 Service。此外,Nest.js 提供了 IOC 容器,統一管理對象的建立和依賴關系,根據聲明來自動注入依賴。

兩者的結合就是通過 @nestjs/typeorm 的包,它有兩個靜态方法用于生成 Module。

說了這麼多,大家可能還了解的不是很清楚,那麼我們就來做下筆記管理的實戰案例吧。

實戰案例

Nest.js 樣闆代碼比較多,自己寫還是比較費事的,@nestjs/cli 的指令行工具對這些做了自動化。

首先要搭項目的骨架,用

nest new project-name
           

複制

然後生成某個 Module 的代碼

nest g resource xxx
           

複制

Nest.js 快速入門:實作對 Mysql 單表的 CRUD

生成的代碼就是帶有 Controller、Service、Module 的,并且也有了 CRUD 的樣闆代碼。

我們重點來看下 Controller 的代碼:

import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
import { XxxService } from './xxx.service';
import { CreateXxxDto } from './dto/create-xxx.dto';
import { UpdateXxxDto } from './dto/update-xxx.dto';

@Controller('xxx')
export class XxxController {
  constructor(private readonly xxxService: XxxService) {}

  @Post()
  create(@Body() createXxxDto: CreateXxxDto) {
    return this.xxxService.create(createXxxDto);
  }

  @Get()
  findAll() {
    return this.xxxService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.xxxService.findOne(+id);
  }

  @Patch(':id')
  update(@Param('id') id: string, @Body() updateXxxDto: UpdateXxxDto) {
    return this.xxxService.update(+id, updateXxxDto);
  }

  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.xxxService.remove(+id);
  }
}
           

複制

@Controller 的參數可以聲明 URL 路徑,@Get、@Post、@Patch、@Delete 也可以通過參數聲明 URL 路徑,最終會把兩個拼起來。比如 /xxx/:id 的 get 方法。

@Get、@Post、@Patch、@Delete 分别對應不同的請求方式。

@Param 是取路徑中的參數,@Query 是取查詢字元串的參數。

@Body 是把請求參數設定到對象的屬性上,被用來傳遞資料的對象叫做 dto(data transfer object)。

再就是傳回的對象會被序列化成 JSON,不需要手動序列化。

然後再看下 Service:

import { Injectable } from '@nestjs/common';
import { CreateXxxDto } from './dto/create-xxx.dto';
import { UpdateXxxDto } from './dto/update-xxx.dto';

@Injectable()
export class XxxService {
  create(createXxxDto: CreateXxxDto) {
    return 'This action adds a new xxx';
  }

  findAll() {
    return `This action returns all xxx`;
  }

  findOne(id: number) {
    return `This action returns a #${id} xxx`;
  }

  update(id: number, updateXxxDto: UpdateXxxDto) {
    return `This action updates a #${id} xxx`;
  }

  remove(id: number) {
    return `This action removes a #${id} xxx`;
  }
}
           

複制

這些 service 的方法都沒有具體實作。

我們引入 Typeorm 來做資料庫的 CRUD。

在根子產品引入用于資料庫連接配接的 Module

Nest.js 快速入門:實作對 Mysql 單表的 CRUD

在剛建立的子產品引入實體對應的 Module:

Nest.js 快速入門:實作對 Mysql 單表的 CRUD

建立筆記實體,用 @Entity 辨別。并且用 @Column、@PrimaryGeneratedColumn 來辨別列和主鍵。

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

@Entity()
export class Note{

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    title: string;

    @Column()
    content: string;

    @Column()
    createTime: Date;

    @Column()
    updateTime: Date;

    @Column()
    isDelete: boolean;
}

           

複制

之後在 service 裡注入實體對應的操作類 Repository,就可以實作對筆記的增删改查了。

Nest.js 快速入門:實作對 Mysql 單表的 CRUD

用到的 dto 就是參數對應的對象,他們是實體的一部分屬性的集合。比如 update dto:

export class UpdateNoteDto {
    title: string;

    content: string;

    createTime: Date;

    updateTime: Date;

    isDelete: boolean;
}
           

複制

這樣,就實作了對筆記的增删改查。

我們用 postman 來測試下效果:

運作

npm start

把項目跑起來

Nest.js 快速入門:實作對 Mysql 單表的 CRUD

可以看到 4 個接口的路由映射都成功了。

資料庫一開始有兩條記錄:

Nest.js 快速入門:實作對 Mysql 單表的 CRUD

通過查詢接口能正确的查出來:

Nest.js 快速入門:實作對 Mysql 單表的 CRUD

然後測試下修改接口:

Nest.js 快速入門:實作對 Mysql 單表的 CRUD

資料庫中确實被修改了:

Nest.js 快速入門:實作對 Mysql 單表的 CRUD

經過測試,對筆記單表的 CRUD 的功能正常。

我們完成了第一個 Nest.js 的後端應用!

完整代碼上傳了 github:https://github.com/QuarkGluonPlasma/nestjs-exercize

總結

Typeorm 是一個 ORM 架構,通過映射表和對象的對應關系,就可以把對對象的操作轉換為對資料庫的操作,自動執行 sql 語句。

Nest.js 是一個 MVC 架構,提供了 Module、Controller、Service 的邏輯劃分,也實作了 IOC 模式,集中管理對象和自動注入依賴。

Typeorm 和 Nest.js 的結合使用 @nestjs/typeorm 的包,它提供了一個 TypeormModule 的子產品,有 forRoot 和 forFeature 兩個靜态方法。forRoot 方法用于生成連接配接資料庫的 Module,forFeature 用于生成實體對應的 Repository 的 Module。

Nest.js 有很多樣闆代碼,可以用 @nestjs/cli 的指令行工具生成,包括整體的和每個 Module 的。

總之,了解了 IOC,了解了 Module、Controller、Service 的劃分,就算是初步掌握了 Nest.js,結合 Typeorm 的 ORM 架構可以輕松的做資料庫表的 CRUD。