js 加入debug后可以进入controller_写给前端的 Nest.js 教程——10分钟上手后端接口开发
前言
沉默了很久,一直都沒發(fā)文章,有些慚愧。
最近實習(xí)結(jié)束之后回了學(xué)校,提前開始做畢業(yè)設(shè)計了。對,就是畢業(yè)設(shè)計。
近兩個月把 React Native、Vue 3.0 和 Nest.js 都摸了一下,大概都摸懂了。
鑒于掘金已經(jīng)很多優(yōu)秀的 Vue 3.0 教程了,本人自認(rèn)為文筆遠(yuǎn)遜于掘金的大佬們,就沒有班門弄斧也寫一篇了(本來很想了,后來想想感覺炒冷飯沒啥意思,如果有想看我的教程風(fēng)格的同學(xué)可以點贊或者在評論區(qū)里留言說一下,也不是不能寫)。
很多后端的同學(xué)都說:你們前端不就是切個圖嘛,憑啥跟我們后端的同學(xué)平起平坐啊?
這下前端的同學(xué)可以站起來了:你們后端不也就是 CRUD 嘛,憑啥瞧不起我們前端的同學(xué)啊?
今天就寫一下最近做畢業(yè)設(shè)計用到的框架:Nest.js 的基礎(chǔ)教程吧,簡單教大家做一下 CRUD(小白向,大佬輕噴)。
挖個坑,這應(yīng)該是最基礎(chǔ)的第一章吧,如果大家覺得好就多點贊評論,過 200 點贊就加更一些,爭取讓大家從前端走向全棧吧。
這個教程的所有代碼我都放在了我的 GitHub 倉庫:Nest-CRUD-Demo,歡迎大家點個 Star!
同時也歡迎大家關(guān)注 「Hello FE」,里面有非常多其他的精品好文,不論是還在學(xué)習(xí)前端的同學(xué)還是已經(jīng)工作了一段時間的朋友,都可以閱讀一下(關(guān)注還有小驚喜,鏈接過期了可以在后臺回復(fù),我看到了會回復(fù)新的鏈接)。框架簡介
Nest 是一個用于構(gòu)建高效,可擴展的 Node.js 服務(wù)器端應(yīng)用程序的框架。它使用漸進(jìn)式 JavaScript,內(nèi)置并完全支持 TypeScript(但仍然允許開發(fā)人員使用純 JavaScript 編寫代碼)并結(jié)合了 OOP(面向?qū)ο缶幊?#xff09;,FP(函數(shù)式編程)和 FRP(函數(shù)式響應(yīng)編程)的元素。在底層,Nest 使用強大的 HTTP Server 框架,如 Express(默認(rèn))和 Fastify。Nest 在這些框架之上提供了一定程度的抽象,同時也將其 API 直接暴露給開發(fā)人員。這樣可以輕松使用每個平臺的無數(shù)第三方模塊。
我猜肯定很多同學(xué)看不懂這段話,沒關(guān)系,我也暫時看不懂,但這不影響我們學(xué)會用它 CRUD。
我們只需要知道它是一款 Node.js 的后端框架,規(guī)范化和開箱即用的特性使其在國外開發(fā)者社區(qū)非常流行,社區(qū)也非常活躍,GitHub Repo 擁有 31.1k Star。
相比于 Express 和 Koa 的千奇百怪五花八門,Nest 確實是一股清流。
不過我們國內(nèi)也有很棒的 Node.js 框架,比如說 Midway,和 Nest 一樣,采用的 IoC 的機制,想了解一下的同學(xué)可以看我的小伙伴林不渡寫的文章:《走近 MidwayJS :初識 TS 裝飾器與 IoC 機制》,還可以到 Midway 官網(wǎng)自行探索。
包括在 Nest 當(dāng)中遇到的裝飾器相關(guān)的知識,大家也可以到上面林不渡同學(xué)的那篇文章中了解。
前置知識
- HTTP
- TypeScript/JavaScript
項目環(huán)境
- git
- mongodb
- node.js >= 10.13.0
安裝 MongoDB
這個章節(jié)的教程我就只寫 Mac OS 上的安裝了,畢竟上了大學(xué)就很少用 Windows 了,用 Windows 的同學(xué)可以到 ="https://mongodb.com/download-center/community">MongoDB 官網(wǎng)選擇對應(yīng)的系統(tǒng)版本去下載 msi 的安裝包,或者搜索引擎里搜索一下,記得限定一下結(jié)果的時間,保證能夠搜索到最新的教程。
強烈建議使用 Homebrew 來對 Mac OS 的軟件包環(huán)境進(jìn)行管理,沒有安裝的同學(xué)可以點擊這里下載。
由于目前 MongoDB 已經(jīng)不開源了,因此我們想要安裝 MongoDB 就只能安裝社區(qū)版本。
brew tap mongodb/brew brew install mongodb-community安裝好之后我們就可以啟動 MongoDB 的服務(wù)了:
brew services start mongodb-community服務(wù)啟動了就不用管了,如果要關(guān)閉的話可以把 start 改成 stop,就能夠停止 MongoDB 的服務(wù)了。
構(gòu)建項目
有兩種方式,可以自行選擇,兩者沒有區(qū)別:
使用 Nest CLI 安裝:
npm i -g @nestjs/cli nest new nest-crud-demo使用 Git 安裝:
git clone https://github.com/nestjs/typescript-starter.git nest-crud-demo這兩條命令的效果完全一致,就是初始化一個 Nest.js 的項目到當(dāng)前文件夾下,項目的文件夾名字為 nest-crud-demo,兩種方式都可以。
當(dāng)然,我還是建議采用第一種方式,因為后面我們可以直接使用腳手架工具生成項目文件。
啟動服務(wù)
cd nest-crud-demo npm run start:dev 或者 yarn run start:dev就可以以開發(fā)模式啟動我們的項目了。
這里其實有一個小小的點,就是啟動的時候應(yīng)該以 dev 模式啟動,這樣 Nest 會自動檢測我們的文件變化,然后自動重啟服務(wù)。
如果是直接 npm start 或者 yarn start 的話,雖然服務(wù)啟動了,但是我們?nèi)绻陂_發(fā)的過程中修改了文件,就要手動停止服務(wù)然后重新啟動,效率挺低的。
安裝依賴
項目中我們會用到 Mongoose 來操作我們的數(shù)據(jù)庫,Nest 官方為我們提供了一個 Mongoose 的封裝,我們需要安裝 mongoose 和 @nestjs/mongoose:
npm install mongoose @nestjs/mongoose --save安裝好之后我們就可以開始編碼過程了。
編寫代碼
創(chuàng)建 Module
我們這次就創(chuàng)建一個 User 模塊,寫一個用戶增刪改查,帶大家熟悉一下這個過程。
nest g module user server腳手架工具會自動在 src/server/user 文件夾下創(chuàng)建一個 user.module.ts,這是 Nest 的模塊文件,Nest 用它來組織整個應(yīng)用程序的結(jié)構(gòu)。
// user.module.ts import { Module } from '@nestjs/common';@Module({}) export class UserModule {}同時還會在根模塊 app.module.ts 中引入 UserModule 這個模塊,相當(dāng)于一個樹形結(jié)構(gòu),在根模塊中引入了 User 模塊。
執(zhí)行上面的終端命令之后,我們會驚訝地發(fā)現(xiàn),app.module.ts 中的代碼已經(jīng)發(fā)生了變化,在文件頂部自動引入了 UserModule,同時也在 @Module 裝飾器的 imports 中引入了 UserModule。
// app.module.ts import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { UserModule } from './server/user/user.module'; // 自動引入@Module({imports: [UserModule], // 自動引入controllers: [AppController],providers: [AppService] }) export class AppModule {}創(chuàng)建 Controller
nest g controller user server在 Nest 中,controller 就類似前端的路由,負(fù)責(zé)處理客戶端傳入的請求和服務(wù)端返回的響應(yīng)。
舉個例子,我們?nèi)绻ㄟ^ http://localhost:3000/user/users 獲取所有的用戶信息,那么我們可以在 UserController 中創(chuàng)建一個 GET 方法,路徑為 users 的路由,這個路由負(fù)責(zé)返回所有的用戶信息。
// user.controller.ts import { Controller, Get } from '@nestjs/common';@Controller('user') export class UserController {@Get('users')findAll(): string {return "All User's Info"; // [All User's Info] 暫時代替所有用戶的信息} }這就是 controller 的作用,負(fù)責(zé)分發(fā)和處理請求和響應(yīng)。
當(dāng)然,也可以把 findAll 方法寫成異步方法,像這樣:
// user.controller.ts import { Controller, Get } from '@nestjs/common';@Controller('user') export class UserController {@Get('users')async findAll(): Promise<any> {return await this.xxx.xxx(); // 一些異步操作} }創(chuàng)建 Provider
nest g service user serverprovider 我們可以簡單地從字面意思來理解,就是服務(wù)的提供者。
怎么去理解這個服務(wù)提供者呢?舉個例子,我們的 controller 接收到了一個用戶的查詢請求,我們不能直接在 controller 中去查詢數(shù)據(jù)庫并返回,而是要將查詢請求交給 provider 來處理,這里我們創(chuàng)建了一個 UserService,就是用來提供數(shù)據(jù)庫操作服務(wù)的。
// user.service.ts import { Injectable } from '@nestjs/common';@Injectable() export class UserService {}當(dāng)然,provider 不一定只能用來提供數(shù)據(jù)庫的操作服務(wù),還可以用來做一些用戶校驗,比如使用 JWT 對用戶權(quán)限進(jìn)行校驗的策略,就可以寫成一個策略類,放到 provider 中,為模塊提供相應(yīng)的服務(wù)。
挺多文檔將 controller 和 provider 翻譯為控制器和提供者,我感覺這種翻譯挺生硬的,讓人不知所云,所以我們姑且記憶他們的英文名吧。
controller 和 provider 都創(chuàng)建完后,我們又會驚奇地發(fā)現(xiàn),user.module.ts 文件中多了一些代碼,變成了這樣:
// user.module.ts import { Module } from '@nestjs/common'; import { UserController } from './user.controller'; import { UserService } from './user.service';@Module({controllers: [UserController],providers: [UserService] }) export class UserModule {}從這里開始,我們就要開始用到數(shù)據(jù)庫了~
連接數(shù)據(jù)庫
引入 Mongoose 根模塊
連接數(shù)據(jù)之前,我們要先在根模塊,也就是 app.module.ts 中引入 Mongoose 的連接模塊:
// app.module.ts import { Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { UserModule } from './server/user/user.module';@Module({imports: [MongooseModule.forRoot('mongodb://localhost/xxx'), UserModule],controllers: [AppController],providers: [AppService] }) export class AppModule {}這段代碼里面的 mongodb://localhost/xxx 其實就是本地數(shù)據(jù)庫的地址,xxx 是數(shù)據(jù)庫的名字。
這時候保存文件,肯定有同學(xué)會發(fā)現(xiàn)控制臺還是報錯的,我們看一下報錯信息就很容易知道問題在哪里了。
其實就是 mongoose 模塊沒有類型聲明文件,這就很容易解決了,安裝一下就好:
npm install @types/mongoose --dev 或者 yarn add @types/mongoose --dev安裝完之后服務(wù)就正常重啟了。
引入 Mongoose 分模塊
這里我們先要創(chuàng)建一個數(shù)據(jù)表的格式,在 src/server/user 文件夾下創(chuàng)建一個 user.schema.ts 文件,定義一個數(shù)據(jù)表的格式:
// user.schema.ts import { Schema } from 'mongoose';export const userSchema = new Schema({_id: { type: String, required: true }, // 覆蓋 Mongoose 生成的默認(rèn) _iduser_name: { type: String, required: true },password: { type: String, required: true } });然后將我們的 user.module.ts 文件修改成這樣:
// user.module.ts import { Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; import { UserController } from './user.controller'; import { userSchema } from './user.schema'; import { UserService } from './user.service';@Module({imports: [MongooseModule.forFeature([{ name: 'Users', schema: userSchema }])],controllers: [UserController],providers: [UserService] }) export class UserModule {}好了,現(xiàn)在一切就緒,終于可以開始編寫我們的 CRUD 邏輯了!沖沖沖~
CRUD
我們打開 user.service.ts 文件,為 UserService 類添加一個構(gòu)造函數(shù),讓其在實例化的時候能夠接收到數(shù)據(jù)庫 Model,這樣才能在類中的方法里操作數(shù)據(jù)庫。
// user.service.ts import { Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; import { CreateUserDTO } from './user.dto'; import { User } from './user.interface';@Injectable() export class UserService {constructor(@InjectModel('Users') private readonly userModel: Model<User>) {}// 查找所有用戶async findAll(): Promise<User[]> {const users = await this.userModel.find();return users;}// 查找單個用戶async findOne(_id: string): Promise<User> {return await this.userModel.findById(_id);}// 添加單個用戶async addOne(body: CreateUserDTO): Promise<void> {await this.userModel.create(body);}// 編輯單個用戶async editOne(_id: string, body: EditUserDTO): Promise<void> {await this.userModel.findByIdAndUpdate(_id, body);}// 刪除單個用戶async deleteOne(_id: string): Promise<void> {await this.userModel.findByIdAndDelete(_id);} }因為 mongoose 操作數(shù)據(jù)庫其實是異步的,所以這里我們使用 async 函數(shù)來處理異步的過程。
好奇的同學(xué)會發(fā)現(xiàn),這里突然出現(xiàn)了兩個文件,一個是 user.interface.ts,另一個是 user.dto.ts,我們現(xiàn)在來創(chuàng)建一下:
// user.interface.ts import { Document } from 'mongoose';export interface User extends Document {readonly _id: string;readonly user_name: string;readonly password: string; } // user.dto.ts export class CreateUserDTO {readonly _id: string;readonly user_name: string;readonly password: string; }export class EditUserDTO {readonly user_name: string;readonly password: string; }其實就是對數(shù)據(jù)類型做了一個定義。
現(xiàn)在,我們可以到 user.controller.ts 中設(shè)置路由了,將客戶端的請求進(jìn)行處理,調(diào)用相應(yīng)的服務(wù)實現(xiàn)相應(yīng)的功能:
// user.controller.ts import {Body,Controller,Delete,Get,Param,Post,Put } from '@nestjs/common'; import { CreateUserDTO, EditUserDTO } from './user.dto'; import { User } from './user.interface'; import { UserService } from './user.service';interface UserResponse<T = unknown> {code: number;data?: T;message: string; }@Controller('user') export class UserController {constructor(private readonly userService: UserService) {}// GET /user/users@Get('users')async findAll(): Promise<UserResponse<User[]>> {return {code: 200,data: await this.userService.findAll(),message: 'Success.'};}// GET /user/:_id@Get(':_id')async findOne(@Param('_id') _id: string): Promise<UserResponse<User>> {return {code: 200,data: await this.userService.findOne(_id),message: 'Success.'};}// POST /user@Post()async addOne(@Body() body: CreateUserDTO): Promise<UserResponse> {await this.userService.addOne(body);return {code: 200,message: 'Success.'};}// PUT /user/:_id@Put(':_id')async editOne(@Param('_id') _id: string,@Body() body: EditUserDTO): Promise<UserResponse> {await this.userService.editOne(_id, body);return {code: 200,message: 'Success.'};}// DELETE /user/:_id@Delete(':_id')async deleteOne(@Param('_id') _id: string): Promise<UserResponse> {await this.userService.deleteOne(_id);return {code: 200,message: 'Success.'};} }至此,我們就完成了一個完整的 CRUD 操作,接下來我們來測試一下~
接口測試
接口測試我們用的是 Postman,大家可以去下載一個,非常好用的接口自測工具。
數(shù)據(jù)庫可視化工具我們用的是 MongoDB 官方的 MongoDB Compass,也很不錯。
GET /user/users
一開始我們的數(shù)據(jù)庫中什么都沒有,所以返回了一個空數(shù)組,沒用用戶信息。
POST /user
現(xiàn)在我們添加一條用戶信息,服務(wù)器返回添加成功。
GET /user/:_id
添加完一條用戶信息之后再查詢,可算是能查詢到我的信息了。
PUT /user/:_id
現(xiàn)在假如我想修改密碼,發(fā)送一個 PUT 請求。
DELETE /user/:_id
現(xiàn)在我們刪除一下剛才添加的用戶信息。
會發(fā)現(xiàn)數(shù)據(jù)庫中的內(nèi)容已經(jīng)被刪除了。
完結(jié)撒花
大功告成,CRUD 就這么簡單,用這個項目去參加一些學(xué)校舉行的比賽,拿個獎肯定沒什么問題,開箱即用(學(xué)校老師們別打我)。
總結(jié)
教程還算是用了比較通俗易懂的方式為大家講解了如何寫一個帶有 CRUD 功能的后端 Node.js 應(yīng)用,框架采用的是 Nest.js。
相信大家在上面的教程中肯定有非常多不懂的部分,比如說 @Get()、@Post()、@Param()、@Body() 等等的裝飾器,再比如說一些 Nest.js 相關(guān)的概念。
沒關(guān)系,我的建議是:學(xué)編程先模仿,遇到不懂的地方先記住,等到自己的積累夠多了,總有一天你會回過頭發(fā)現(xiàn)自己茅塞頓開,突然懂了。這也是我個人學(xué)習(xí)的一個小技巧。
在學(xué)習(xí)的過程中,也一定會遇到一些問題,學(xué)習(xí)編程的過程中遇到問題不能自己憋著,一定要學(xué)會請教大佬!一定要學(xué)會請教大佬!一定要學(xué)會請教大佬!重要的事情說三遍。
不過也別很簡單的問題就去請教大佬,而且最好給一點小小的報酬,畢竟誰也沒有義務(wù)幫你解決問題。
我在學(xué)習(xí)的過程中也請教了一些社區(qū)里面的大佬,同時還進(jìn)入了 Nest.js 的社區(qū)答疑群,向國外友人請教學(xué)到了不少知識。
當(dāng)然,這個 Demo 中也有很多可以完善的地方,比如說錯誤處理。
數(shù)據(jù)庫的操作肯定是有可能出現(xiàn)錯誤的,比如說我們漏傳了 required: true 的參數(shù),數(shù)據(jù)庫就會報錯。
這個時候我們就要寫一個 try/catch 捕獲這個異常,或者干脆寫一個異常的過濾器,將所有的異常統(tǒng)一處理(Nest.js 支持過濾器)
除此之外,既然有可能出現(xiàn)異常,那么我們就需要一個日志系統(tǒng)去捕獲這個異常,方便查錯糾錯。
如果涉及到登錄注冊的部分,還有密碼加解密的過程,同時還可能有權(quán)限校驗問題需要進(jìn)行處理。
所以后端的同學(xué)肯定不止 CRUD 啦(可算圓回來了)。
這個教程的所有代碼我都放在了我的 GitHub 倉庫:Nest-CRUD-Demo,歡迎大家點個 Star!
參考資料
- NestJS - A progressive Node.js framework
- Nest.js 中文文檔
總結(jié)
以上是生活随笔為你收集整理的js 加入debug后可以进入controller_写给前端的 Nest.js 教程——10分钟上手后端接口开发的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux 别名,Linux中的别名就这
- 下一篇: 学习Java编程-Java Timezo