Nest.js 快速入門:實現(xiàn)對 MySQL 單表的 Crud
Nest.js 是一個 Node.js 的后端開發(fā)框架,它實現(xiàn)了 MVC 模式,也支持了 IOC(自動注入依賴),比 Express 這類處理請求響應(yīng)的庫高了一個層次。而且也很容易集成 GraphQL、WebSocket 等功能,適合用來做大規(guī)模企業(yè)級開發(fā)。
Nest.js 在國內(nèi)外用的都挺多的,今天我們就來入門下吧:做一個筆記管理功能,實現(xiàn)對 mysql 單表的增刪改查并提供 Restful 的接口。
后面要介紹的內(nèi)容比較多,我們先來看下最終的效果吧:
完整代碼上傳了 github:https://github.com/QuarkGluonPlasma/nestjs-exercize
Nest.js + Typeorm 基礎(chǔ)
mysql 數(shù)據(jù)庫和 Typeorm
首先從離前端比較遠的數(shù)據(jù)庫講起。
在 mysql 的官網(wǎng)下載 mysql,安裝并啟動服務(wù)。
這時候就可以用命令行來寫 sql 操作數(shù)據(jù)庫了。
但是命令行操作不夠方便,所以我們還要下載一個有界面的 mysql 客戶端,我這里用的是 navicat。
它可以可視化的創(chuàng)建數(shù)據(jù)庫、表等,可以在編輯器里寫 sql 然后執(zhí)行。比如圖中我創(chuàng)建了 hello 的數(shù)據(jù)庫和一堆表。
Node.js 代碼里同樣可以連接上數(shù)據(jù)庫服務(wù),然后遠程執(zhí)行 sql 來對數(shù)據(jù)庫表做增刪改查。
但直接執(zhí)行 sql 比較繁瑣,能不能我只操作對象,對象屬性變了就自動去執(zhí)行 sql 來同步數(shù)據(jù)庫呢?就像 vue 的數(shù)據(jù)變了自動同步視圖一樣。
數(shù)據(jù)庫和對象關(guān)系的映射就叫做 ORM(Object Relational Mapping),也就是把表映射成對象,把表與表之間的關(guān)聯(lián)映射成對象之間的關(guān)系。之后對對象的操作會通過 sql 同步到數(shù)據(jù)庫。
Typeorm 就是一個實現(xiàn) orm 的框架,可以通過裝飾器來描述映射關(guān)系,比如 @Entity(實體)、@Column(列)、@PrimaryGeneratedColumn(主鍵 ID 自動生成)
- import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";
- @Entity()
- export class Note{
- @PrimaryGeneratedColumn()
- id: number;
- @Column()
- title: string;
- @Column()
- content: string;
- }
通過裝飾器聲明了關(guān)系,那么在建立了數(shù)據(jù)庫連接之后,我們只需要操作對象,Typeorm 就會自動去執(zhí)行 sql 來把變動同步到數(shù)據(jù)庫。
這樣,我們對數(shù)據(jù)庫的表的操作和增刪改查就實現(xiàn)了。
數(shù)據(jù)庫部分搞定之后,我們再往前看一下處理請求的部分。
http 請求和 Nest.js
處理請求的后端框架我們使用 Nest.js,它提供了 Controller、Service 等劃分,這是對 MVC 模式的實現(xiàn)。
Controller 里面負責(zé)處理請求,把處理過的參數(shù)傳遞給 service。
Service 負責(zé)業(yè)務(wù)邏輯的實現(xiàn),基于 Typeorm 的增刪改查功能來實現(xiàn)各種上層業(yè)務(wù)邏輯。
除此以外,Nest.js 還劃分了 Module,這個 Module 是邏輯上的模塊,和我們常說的文件對應(yīng)的模塊不同,它包含了 Controller、Service 等,是對這些資源的邏輯劃分。
Module 和 Module 之間還可以有依賴關(guān)系,也就有 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,同時,還導(dǎo)出了 BbbService 可以被其他模塊引入。
Controller 的聲明也是通過裝飾器:
- @Controller()
- export class BbbController {
- }
Service 的聲明也是用裝飾器,只不過不叫 Service,而叫 Injectable。
- @Injectable()
- export class BbbService {
- }
至于為什么叫 Injectable,就涉及到了 IOC 的概念了。
IOC(Inverse Of Control)是控制反轉(zhuǎn)的意思,就是只需要聲明你的依賴,不需要創(chuàng)建依賴的對象,容器會注入給你。
因為所有的對象都是由容器管理的,那么自然就可以在創(chuàng)建對象的時候注入它需要的依賴,這就是 IOC 的原理。
Service 是可以被作為依賴注入到其他類的實例中去的,所以用 Injectable 裝飾器。
所有的 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 開始創(chuàng)建 IOC 容器,處理從 3000 端口發(fā)過來的請求。
reflect-metadata 模塊是用于解析類的裝飾器的,因為要給某個類的實例注入依賴就得能解析出它通過裝飾器聲明了哪些依賴,然后注入給它。所以要實現(xiàn) IOC 需要依賴這個包。
這就是 Nest.js 大概的設(shè)計了:IOC + MVC,通過 IOC 容器來管理對象的依賴關(guān)系,通過 Controller、Service、Module 來做職責(zé)上的劃分。
Nest.js 結(jié)合 Typeorm
Typeorm 是做把對象的操作通過 sql 同步為對數(shù)據(jù)庫操作的 orm 的,而 Nest.js 是做 Web 后端應(yīng)用的 MVC 分層以及通過 IOC 管理對象的創(chuàng)建和依賴的。這倆很自然的可以結(jié)合,結(jié)合的方式就是 @nestjs/typeorm 包。
@nestjs/typeorm 包提供了 TypeOrmModule 這個 Module,它有兩個靜態(tài)方法 forRoot、forFeature。
forRoot 用于創(chuàng)建數(shù)據(jù)庫連接,傳入一些配置參數(shù),在入口 Module 引入。
- @Module({
- imports: [
- TypeOrmModule.forRoot({
- type: 'mysql',
- host: 'localhost',
- port: 3306,
- username: 'root',
- password: '你的密碼',
- database: '數(shù)據(jù)庫名',
- synchronize: true
- }),
- NotesModule
- ]
- })
- export class AppModule {}
forFeature 用于創(chuàng)建不同實體類對應(yīng)的 Repository,在用到該實體的 Module 里引入。
- @Module({
- imports: [TypeOrmModule.forFeature([Aaa])],
- controllers: [AaaController],
- providers: [AaaService],
- exports: [AaaService]
- })
- export class AaaModule {}
我們知道了 Typeorm 和 Nest.js 都是做什么的和怎么用,簡單小結(jié)一下:
Typeorm 是 ORM 框架,用于把對象的操作同步為對數(shù)據(jù)庫的操作,會自動執(zhí)行 sql 語句。
Nest.js 是 MVC 框架,用于 Web 后端應(yīng)用的邏輯分層,還提供了 Module 用來進一步劃分 Controller 和 Service。此外,Nest.js 提供了 IOC 容器,統(tǒng)一管理對象的創(chuàng)建和依賴關(guān)系,根據(jù)聲明來自動注入依賴。
兩者的結(jié)合就是通過 @nestjs/typeorm 的包,它有兩個靜態(tài)方法用于生成 Module。
說了這么多,大家可能還理解的不是很清楚,那么我們就來做下筆記管理的實戰(zhàn)案例吧。
實戰(zhàn)案例
Nest.js 樣板代碼比較多,自己寫還是比較費事的,@nestjs/cli 的命令行工具對這些做了自動化。
首先要搭項目的骨架,用
- nest new project-name
然后生成某個 Module 的代碼
- nest g resource xxx
生成的代碼就是帶有 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 的參數(shù)可以聲明 URL 路徑,@Get、@Post、@Patch、@Delete 也可以通過參數(shù)聲明 URL 路徑,最終會把兩個拼起來。比如 /xxx/:id 的 get 方法。
@Get、@Post、@Patch、@Delete 分別對應(yīng)不同的請求方式。
@Param 是取路徑中的參數(shù),@Query 是取查詢字符串的參數(shù)。
@Body 是把請求參數(shù)設(shè)置到對象的屬性上,被用來傳遞數(shù)據(jù)的對象叫做 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 的方法都沒有具體實現(xiàn)。
我們引入 Typeorm 來做數(shù)據(jù)庫的 CRUD。
在根模塊引入用于數(shù)據(jù)庫連接的 Module
在剛創(chuàng)建的模塊引入實體對應(yīng)的 Module:
創(chuàng)建筆記實體,用 @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 里注入實體對應(yīng)的操作類 Repository,就可以實現(xiàn)對筆記的增刪改查了。
用到的 dto 就是參數(shù)對應(yīng)的對象,他們是實體的一部分屬性的集合。比如 update dto:
- export class UpdateNoteDto {
- title: string;
- content: string;
- createTime: Date;
- updateTime: Date;
- isDelete: boolean;
- }
這樣,就實現(xiàn)了對筆記的增刪改查。
我們用 postman 來測試下效果:
運行 npm start 把項目跑起來
可以看到 4 個接口的路由映射都成功了。
數(shù)據(jù)庫一開始有兩條記錄:
通過查詢接口能正確的查出來:
然后測試下修改接口:
數(shù)據(jù)庫中確實被修改了:
經(jīng)過測試,對筆記單表的 CRUD 的功能正常。
我們完成了第一個 Nest.js 的后端應(yīng)用!
完整代碼上傳了 github:https://github.com/QuarkGluonPlasma/nestjs-exercize
總結(jié)
Typeorm 是一個 ORM 框架,通過映射表和對象的對應(yīng)關(guān)系,就可以把對對象的操作轉(zhuǎn)換為對數(shù)據(jù)庫的操作,自動執(zhí)行 sql 語句。
Nest.js 是一個 MVC 框架,提供了 Module、Controller、Service 的邏輯劃分,也實現(xiàn)了 IOC 模式,集中管理對象和自動注入依賴。
Typeorm 和 Nest.js 的結(jié)合使用 @nestjs/typeorm 的包,它提供了一個 TypeormModule 的模塊,有 forRoot 和 forFeature 兩個靜態(tài)方法。forRoot 方法用于生成連接數(shù)據(jù)庫的 Module,forFeature 用于生成實體對應(yīng)的 Repository 的 Module。
Nest.js 有很多樣板代碼,可以用 @nestjs/cli 的命令行工具生成,包括整體的和每個 Module 的。
總之,理解了 IOC,理解了 Module、Controller、Service 的劃分,就算是初步掌握了 Nest.js,結(jié)合 Typeorm 的 ORM 框架可以輕松的做數(shù)據(jù)庫表的 CRUD。