NestJS快速入門
NestJS 是一個基于 TypeScript 的開發架構,它建立在 Express、Fastify 和其他 Node.js Web 架構之上,提供了一種用于建構高效、可擴充的伺服器端應用程式的方式。相比于傳統的 Express 和 Koa 架構,NestJS 提供了更多的功能和抽象層,使得開發者可以更輕松地建構複雜的應用程式。
NestJS、Koa 和 Express 的一些對比:
- 架構模式:NestJS 使用了基于子產品化的架構模式,它将應用程式劃分為子產品、控制器和提供者。這種模式使得代碼的組織和維護更加容易。而 Koa 和 Express 則沒有明确的架構模式,開發者需要自己組織代碼結構。
- TypeScript 支援:NestJS 是一個完全基于 TypeScript 的架構,它提供了強大的靜态類型檢查和面向對象程式設計的能力。而 Koa 和 Express 都是基于 JavaScript 的架構,雖然可以使用 TypeScript,但是缺乏一些 TypeScript 特有的功能和類型檢查。
- 中間件支援:Koa 和 Express 都有強大的中間件支援,可以友善地添加和進行中間件。NestJS 也支援中間件,但是它使用了自己的裝飾器和管道機制來進行中間件,使得中間件的使用更加靈活。
- 異步程式設計:NestJS 提供了強大的異步程式設計支援,包括異步子產品、異步提供者和異步管道等。這使得開發者可以更好地處理異步操作,如資料庫查詢、網絡請求等。Koa 和 Express 也支援異步程式設計,但是相對于 NestJS 來說,它們的異步支援相對較弱。
總體而言,NestJS 提供了更多的功能和抽象層,使得開發者可以更輕松地建構複雜的應用程式。如果你喜歡使用 TypeScript、需要更強大的架構模式和異步程式設計支援,那麼 NestJS 是一個很好的選擇。如果你對簡潔性和靈活性更加看重,那麼 Koa 和 Express 也是不錯的選擇。最終選擇哪個架構還要根據具體的項目需求和個人偏好來決定。
NestJS 環境搭建和項目建立
bash複制代碼# node 官網檢視安裝方法 https://nodejs.org
# 全局安裝NestJS CLI
npm i -g @nestjs/cli
# 建立NestJS項目
nest new nest-best
目錄說明
bash複制代碼├── dist # 編譯後的目錄,打包産物
├── node_modules # 項目依賴包
├── REST_Client # 結合 vs code 插件 REST Client 調試接口示例
├── src # 源碼目錄
│ ├── app.controller.spec.ts # 對于基本控制器的單元測試樣例
│ ├── app.controller.ts # 控制器檔案,可以簡單了解為路由檔案
│ ├── app.module.ts # 子產品檔案,在NestJS世界裡主要操作的就是子產品
│ ├── app.service.ts # 服務檔案,提供的服務檔案,業務邏輯編寫在這裡
│ └── main.ts # 項目的入口檔案,裡邊包括項目的主子產品和監聽端口号
├── test # 測試檔案目錄,對項目測試時使用的目錄,比如單元測試...
│ ├── app.e2e-spec.ts # e2e測試,端對端測試檔案,測試流程和功能使用
│ └── jest-e2e.json # jest 配置檔案
├── .eslintrc.js # eslint 配置
├── .gitignore # git忽略檔案
├── .prettierrc # prettier 配置
├── nest-cli.json # 整個項目的配置檔案,這個需要根據項目進行不同的配置
├── package.json # 項目依賴包管理檔案和Script檔案,比如如何啟動項目的指令
├── tsconfig.build.json # TypeScript文法建構時的配置檔案
├── tsconfig.json # TypeScript的配置檔案,控制TypeScript編譯器的一些行為
└── README.md # 文檔說明
vscode 插件 【REST Client】
bash複制代碼# http 示例目錄
REST_Client
基礎指令
開始
bash複制代碼# development
$ npm run start
# watch mode
$ npm run start:dev
# production mode
$ npm run start:prod
測試
bash複制代碼# unit tests
$ npm run test
# e2e tests
$ npm run test:e2e
# test coverage
$ npm run test:cov
部署
1、項目根目錄建立 Dockerfile
Dockerfile複制代碼# 使用Node.js作為基礎鏡像
FROM node:20-alpine3.18
# 設定工作目錄
WORKDIR /app
# 複制package.json和package-lock.json檔案到工作目錄
COPY package*.json ./
# npm 源,選用國内鏡像源以提高下載下傳速度
RUN npm config set registry https://registry.npm.taobao.org/
# 安裝依賴
RUN npm install
# 複制應用程式的源代碼到工作目錄
COPY . .
# 暴露應用程式的端口
EXPOSE 3000
# 啟動應用程式
CMD ["npm", "run", "start:prod"]
bash複制代碼# 2、導航到您的NestJS應用程式的根目錄,并運作以下指令來建構Docker鏡像:
docker build -t nest-best .
# 3、使用以下指令在Docker容器中運作您的NestJS應用程式:
docker run -d -p 3000:3000 nest-best -name nest-best
# docker run: 運作一個新的容器。
# -d: 在背景運作容器。
# -p 3000:3000: 将容器的端口3000映射到主機的端口3000,這樣可以通過主機的端口通路容器中運作的應用程式。
# nest-best: 要運作的容器的名稱或鏡像。
# -name nest-best: 設定容器的名稱為"nest-best"。
http://81.71.98.176:3000/ ,看到 Hello World!,說明部署成功。
入門
建立一個 book 子產品和路由
bash複制代碼nest g module book
# 用指令行建立 controller
nest g controller book --no-spec
# 用指令行建立 service
nest g service book --no-spec
Get、Post、Request、Query、Body、Param、Headers 修飾器
ORM - 操作資料庫
ORM:定義一個對象,這個對象就對應着一張表,這個對象的一個執行個體,就對應着表中的一條記錄。
docker 安裝 mysql
bash複制代碼# 安裝 https://www.runoob.com/docker/docker-install-mysql.html
# 拉取最新鏡像
docker pull mysql:latest
# 運作容器
# MYSQL_ROOT_PASSWORD=123456:設定 MySQL 服務 root 使用者的密碼。
docker run -itd --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql
# 修改容器時區
# 進入容器
docker exec -u 0 -it 容器ID bash
# 執行
mv /etc/localtime /etc/localtime.bak && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
TypeORM
bash複制代碼# 安裝
npm install --save @nestjs/typeorm typeorm mysql2
我們直接再/src/app.module.ts 中引入 typeorm。
ts複制代碼import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql', // 資料庫類型
host: '81.71.98.176', // 資料庫的連接配接位址 host
port: 3306, // 資料庫的端口 3306
username: 'root',
password: '******',
database: 'test', // 連接配接的資料庫
retryDelay: 500, // 重試連接配接資料庫間隔
retryAttempts: 10, // 允許重連次數
synchronize: true, // 是否将實體同步到資料庫
autoLoadEntities: true, // 自動加載實體配置,forFeature()注冊的每個實體都自己動加載
}),
],
controllers: [],
providers: [],
})
export class AppModule {}
編寫 Entities 實體
/src/book/ectities/book.entity.ts
ts複制代碼import {
Entity,
Column,
PrimaryGeneratedColumn,
CreateDateColumn,
} from 'typeorm';
@Entity()
export class Book {
@PrimaryGeneratedColumn('uuid')
id: number;
@Column({ type: 'varchar', length: 255 })
name: string;
@Column({ type: 'varchar' })
description: string;
@CreateDateColumn({ type: 'timestamp' })
createTime: Date;
}
編寫好類之後,需要在 book.module.ts 裡進行引入
ts複制代碼import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Book } from './entities/book.entity';
@Module({
imports: [TypeOrmModule.forFeature([Book])],
...
})
export class BookModule {}
對資料庫的增删改查 book.service.ts
ts複制代碼// 依賴導入
import { Injectable } from '@nestjs/common';
import { Like, Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Book } from './entities/book.entity';
引入後我們要在構造函數裡增加一個依賴注入的操作。
ts複制代碼export class BookService {
// 依賴注入
constructor(
@InjectRepository(Book) private readonly book: Repository<Book>,
) {}
...
}
完整增删改查示例
ts複制代碼// book.service.ts
import { Injectable } from '@nestjs/common';
import { Like, Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Book } from './entities/book.entity';
@Injectable()
export class BookService {
// 依賴注入
constructor(
@InjectRepository(Book) private readonly book: Repository<Book>,
) {}
async getBooks() {
const data = await this.book.find();
return {
code: 200,
data: data,
msg: 'success',
};
}
async addBook(body: any) {
const book = new Book();
const { name, description } = body;
book.name = name;
book.description = description;
const data = await this.book.save(book);
return {
code: 200,
data: data,
msg: '添加書籍成功',
};
}
async deleteBook(id: number) {
await this.book.delete(id);
return {
code: 200,
data: null,
msg: '删除書籍成功',
};
}
async updateBook(id: number, body: any) {
const book = new Book();
const { name, description } = body;
book.name = name;
book.description = description;
await this.book.update(id, book);
return {
code: 200,
data: null,
msg: '更新書籍成功',
};
}
async getBookByName(name: string) {
const data = await this.book.find({
where: {
name: Like(`%${name}%`),
},
});
return {
code: 200,
data: data,
msg: 'success',
};
}
async getBookById(id: any) {
const data = await this.book.findOneBy({
id,
});
return {
code: 200,
data: data,
msg: 'success',
};
}
}
ts複制代碼// book.controller.ts
import {
Controller,
Get,
Post,
Request,
Query,
Body,
Param,
Headers,
} from '@nestjs/common';
import { BookService } from './book.service';
@Controller('book')
export class BookController {
constructor(private readonly bookService: BookService) {}
@Get()
getBooks(): any {
return this.bookService.getBooks();
}
@Post('/add')
addBook(@Body() body): any {
return this.bookService.addBook(body);
}
@Get('/delete/:id')
deleteBook(@Param() param): any {
return this.bookService.deleteBook(param.id);
}
@Post('/update/:id')
updateBook(@Param() param, @Body() body): any {
return this.bookService.updateBook(param.id, body);
}
@Post('/getBookByName')
getBookByName(@Body() body): any {
console.log('body.name', body.name);
return this.bookService.getBookByName(body.name);
}
@Get('/getBookById')
getBookById(@Request() req): any {
// 因為通過Get方式傳遞過來的是字元串,所有我們需要用parseInt轉化為數字
const id: number = parseInt(req.query.id);
return this.bookService.getBookById(id);
}
}
Providers(提供者)實作依賴注入
自定義注入值
ts複制代碼// book.module.ts 注入
providers: [
{
provide: 'Category',
useValue: ['武俠', '修仙', '都市'],
},
],
// book.controller.ts 使用
constructor(@Inject('Category') private categorys: string[]) {}
@Get('/category')
getCategorys(): string[] {
return this.categorys;
}
// test
http://localhost:3000/book/category
自定義工廠(方法)
ts複制代碼// book.module.ts 注入
providers: [
...,
{
provide: 'MyFactory',
useFactory() {
return '自定義工廠(方法)';
},
},
],
// book.controller.ts 使用
constructor(
...,
@Inject('MyFactory') private myFactory: string,
) {}
@Get('/category')
getCategorys(): string[] {
console.log(this.myFactory);
return this.categorys;
}
增加熱重載功能
bash複制代碼# 安裝依賴
npm i --save-dev webpack-node-externals run-script-webpack-plugin webpack @types/webpack-env
js複制代碼// 項目根目錄下增加一個webpack-hmr.config.js的配置檔案
const nodeExternals = require('webpack-node-externals');
const { RunScriptWebpackPlugin } = require('run-script-webpack-plugin');
module.exports = function (options, webpack) {
return {
...options,
entry: ['webpack/hot/poll?100', options.entry],
externals: [
nodeExternals({
allowlist: ['webpack/hot/poll?100'],
}),
],
plugins: [
...options.plugins,
new webpack.HotModuleReplacementPlugin(),
new webpack.WatchIgnorePlugin({
paths: [/\.js$/, /\.d\.ts$/],
}),
new RunScriptWebpackPlugin({
name: options.output.filename,
autoRestart: false,
}),
],
};
};
bash複制代碼# 替換啟動腳本
"start:dev": "nest build --webpack --webpackPath webpack-hmr.config.js --watch",
# 重新啟動項目
npm run start:dev
然後在/src/book/book.controller.ts 檔案裡增加一個 hotLoad() 方法測試。
ts複制代碼@Get('/hotLoad')
hotLoad(): any {
return 'HotLoad Function';
}
中間件 Middleware
局部中間件
bash複制代碼# 指令生成
nest g mi counter
# CREATE src/counter/counter.middleware.spec.ts
# CREATE src/counter/counter.middleware.ts
ts複制代碼// counter.middleware.ts 列印一下
console.log('進入中間件...');
next(); // 必須調用 next() 方法,否則請求無法繼續
ts複制代碼// book.module.ts使用
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { CounterMiddleware } from '../counter/counter.middleware';
export class BookModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(CounterMiddleware).forRoutes('book');
}
}
發起請求 http://localhost:3000/book/list ,控制台看到輸出,說明中間件起作用了。
全局中間件
局部中間件我們是以實作 NestModule 接口的形式,把中間件挂在到 Moudle 上的.而全局中間件要以 function 的形式 編寫到 main.ts 裡。
ts複制代碼import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
// 定義全局中間件方法
function MiddleWareAll(req: any, res: any, next: any) {
console.log('我是全局中間件.....');
next();
}
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 使用全局中間件
app.use(MiddleWareAll);
await app.listen(3000);
if (module.hot) {
module.hot.accept();
module.hot.dispose(() => app.close());
}
}
bootstrap();
發起請求 http://localhost:3000/hi ,控制台看到輸出,說明中間件起作用了。
中間件 Middleware-用第三方中間件實作跨域
NestJS 是自帶配置項的app.enableCors(); (main.ts),可以通過簡單的配置完成跨域操作。這裡主要是學習一下 use 第三方中間件。
bash複制代碼# 安裝依賴
npm install cors
npm install @types/cors -D
ts複制代碼// app.controller.ts
@Get('/corstest')
corsTest(): object {
return { message: '測試跨域請求成功' };
}
// main.ts
import * as cors from 'cors';
app.use(cors());
打開 www.baidu.com, f12 控制台
ts複制代碼// 設定跨域前後都調用一下
fetch('http://localhost:3000/book/list')
.then((res) => res.json())
.then((res) => {
console.log(res);
});
子產品 Module
子產品之間互相調用(共享)
bash複制代碼# 建立 book2 子產品
nest g module book2
# 用指令行建立 controller
nest g controller book2 --no-spec
# 用指令行建立 service
nest g service book2 --no-spec
ts複制代碼// book2.service.ts 增加 getBook2 方法
import { Injectable } from '@nestjs/common';
@Injectable()
export class Book2Service {
async getBook2() {
return {
code: 200,
data: '調用 Book2Service 方法成功',
msg: 'success',
};
}
}
ts複制代碼// book2.module.ts 導出 Book2Service
import { Module } from '@nestjs/common';
import { Book2Controller } from './book2.controller';
import { Book2Service } from './book2.service';
@Module({
controllers: [Book2Controller],
providers: [Book2Service],
exports: [Book2Service],
})
export class Book2Module {}
ts複制代碼// 在 book.module.ts 引入 Book2Service
// 然後注入 providers
import { Book2Service } from '../book2/book2.service';
@Module({
providers: [
Book2Service,
...
],
})
ts複制代碼// 最後在 book.controller.ts 進行引入使用
import { Book2Service } from 'src/book2/book2.service';
@Controller('book')
export class BookController {
...
// 調用 book2Service 方法
@Get('/getBook2')
getBook2(): any {
return this.book2Service.getBook2();
}
}
調用 http://localhost:3000/book/getBook2,看到結果傳回,即實作了跨子產品調用
全局子產品
bash複制代碼# 建立子產品
nest g module common
ts複制代碼// common.module.ts
import { Module, Global } from '@nestjs/common';
@Global()
@Module({
providers: [
{
provide: 'Common',
useValue: { author: 'Leslie' },
},
],
exports: [
{
provide: 'Common',
useValue: { author: 'Leslie' },
},
],
})
export class CommonModule {}
ts複制代碼// 在 app.module.ts 引入 全局子產品
import { CommonModule } from './common/common.module';
@Module({
imports: [
...,
CommonModule,
],
})
export class AppModule {}
ts複制代碼// book.controller.ts 中依賴注入,并使用
@Controller('book')
export class BookController {
constructor(
...,
@Inject('Common') private Common: any,
) {}
// 調用全局子產品方法
@Get('/author')
getAuthor(): any {
const author = `作者: ${this.Common?.author}`;
return {
code: 200,
data: author,
};
}
}
調用 http://localhost:3000/book/author 驗證。