<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          寫給前端的 Nest.js 教程——10分鐘上手后端接口開發(fā)

          共 10890字,需瀏覽 22分鐘

           ·

          2020-10-28 06:44

          前言

          很多后端的同學都說:「你們前端不就是切個圖嘛,憑啥跟我們后端的同學平起平坐???」

          這下前端的同學可以站起來了:「你們后端不也就是 CRUD 嘛,憑啥瞧不起我們前端的同學啊?」

          今天就寫一下最近做「畢業(yè)設(shè)計」用到的框架:Nest.js 的基礎(chǔ)教程吧,簡單教大家做一下 CRUD(小白向,大佬輕噴)。

          挖個坑,這應(yīng)該是最基礎(chǔ)的「第一章」吧,「如果大家覺得好就多點贊評論」,過 「200 點贊」就加更一些,「爭取讓大家從前端走向全棧吧」。

          這個教程的所有代碼我都放在了我的 GitHub 倉庫:Nest-CRUD-Demo[1],歡迎大家點個 Star!

          ?

          同時也歡迎大家關(guān)注 「「Hello FE」」,里面有非常多其他的精品好文,不論是還在學習前端的同學還是已經(jīng)工作了一段時間的朋友,都可以閱讀一下(關(guān)注還有小驚喜,鏈接過期了可以在后臺回復,我看到了會回復新的鏈接)。

          ?

          框架簡介

          ?

          Nest 是一個用于構(gòu)建高效,可擴展的 Node.js 服務(wù)器端應(yīng)用程序的框架。它使用漸進式 JavaScript,內(nèi)置并完全支持 TypeScript(但仍然允許開發(fā)人員使用純 JavaScript 編寫代碼)并結(jié)合了 OOP(面向?qū)ο缶幊蹋?code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">FP(函數(shù)式編程)和 FRP(函數(shù)式響應(yīng)編程)的元素。

          在底層,Nest 使用強大的 HTTP Server 框架,如 Express(默認)和 Fastify。Nest 在這些框架之上提供了一定程度的抽象,同時也將其 API 直接暴露給開發(fā)人員。這樣可以輕松使用每個平臺的無數(shù)第三方模塊。

          ?

          我猜肯定很多同學看不懂這段話,沒關(guān)系,我也暫時看不懂,但這不影響我們學會用它 CRUD

          我們只需要知道它是一款 Node.js 的后端框架,「規(guī)范化」「開箱即用」的特性使其在國外開發(fā)者社區(qū)非常流行,社區(qū)也非常活躍,GitHub Repo[2] 擁有 31.1k Star。

          相比于 ExpressKoa 的千奇百怪五花八門,Nest 確實是一股清流。

          不過我們國內(nèi)也有很棒的 Node.js 框架,比如說 Midway,和 Nest 一樣,采用的 IoC 的機制,想了解一下的同學可以看我的小伙伴「林不渡」寫的文章:《走近 MidwayJS :初識 TS 裝飾器與 IoC 機制》[3],還可以到 Midway 官網(wǎng)[4]自行探索。

          包括在 Nest 當中遇到的裝飾器相關(guān)的知識,大家也可以到上面「林不渡」同學的那篇文章中了解。

          前置知識

          • HTTP
          • TypeScript/JavaScript

          項目環(huán)境

          • git
          • mongodb
          • node.js >= 10.13.0

          安裝 MongoDB

          這個章節(jié)的教程我就只寫 Mac OS 上的安裝了,畢竟上了大學就很少用 Windows 了,用 Windows 的同學可以到 `MongoDB` 官網(wǎng)[5]選擇對應(yīng)的系統(tǒng)版本去下載 msi 的安裝包,或者「搜索引擎」里搜索一下,記得限定一下結(jié)果的時間,保證能夠搜索到最新的教程。

          強烈建議使用 Homebrew 來對 Mac OS 的軟件包環(huán)境進行管理,沒有安裝的同學可以點擊這里[6]下載。

          由于目前 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 的項目到當前文件夾下,項目的文件夾名字為 nest-crud-demo,兩種方式都可以。

          「當然,我還是建議采用第一種方式,因為后面我們可以直接使用腳手架工具生成項目文件。」

          啟動服務(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 這個模塊,相當于一個樹形結(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 就類似前端的「路由」,負責處理「客戶端傳入的請求」「服務(wù)端返回的響應(yīng)」

          舉個例子,我們?nèi)绻ㄟ^ http://localhost:3000/user/users 獲取所有的用戶信息,那么我們可以在 UserController 中創(chuàng)建一個 GET 方法,路徑為 users 的路由,這個路由負責返回所有的用戶信息。

          //?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ā)和處理「請求」「響應(yī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?server

          provider 我們可以簡單地從字面意思來理解,就是「服務(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?{}

          當然,provider 不一定只能用來提供數(shù)據(jù)庫的操作服務(wù),還可以用來做一些用戶校驗,比如使用 JWT 對用戶權(quán)限進行校驗的策略,就可以寫成一個策略類,放到 provider 中,為模塊提供相應(yīng)的服務(wù)。

          挺多文檔將 controllerprovider 翻譯為「控制器」「提供者」,我感覺這種翻譯挺生硬的,讓人不知所云,所以我們姑且記憶他們的英文名吧。

          controllerprovider 都創(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ù)庫的名字。

          這時候保存文件,肯定有同學會發(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?生成的默認?_id
          ??user_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, EditUserDTO }?from?'./user.dto';
          import?{?User?}?from?'./user.interface';

          @Injectable()
          export?class?UserService?{
          ??constructor(@InjectModel('Users')?private?readonly?userModel:?Model)?{}

          ??//?查找所有用戶
          ??async?findAll():?Promise?{
          ????const?users?=?await?this.userModel.find();
          ????return?users;
          ??}

          ??//?查找單個用戶
          ??async?findOne(_id:?string):?Promise?{
          ????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ù)來處理異步的過程。

          好奇的同學會發(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è)置路由了,將「客戶端的請求」進行處理,調(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?{
          ??code:?number;
          ??data?:?T;
          ??message:?string;
          }

          @Controller('user')
          export?class?UserController?{
          ??constructor(private?readonly?userService:?UserService)?{}

          ??//?GET?/user/users
          ??@Get('users')
          ??async?findAll():?Promise>?{
          ????return?{
          ??????code:?200,
          ??????data:?await?this.userService.findAll(),
          ??????message:?'Success.'
          ????};
          ??}

          ??//?GET?/user/:_id
          ??@Get(':_id')
          ??async?findOne(@Param('_id')?_id:?string):?Promise>?{
          ????return?{
          ??????code:?200,
          ??????data:?await?this.userService.findOne(_id),
          ??????message:?'Success.'
          ????};
          ??}

          ??//?POST?/user
          ??@Post()
          ??async?addOne(@Body()?body:?CreateUserDTO):?Promise?{
          ????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?{
          ????await?this.userService.editOne(_id,?body);
          ????return?{
          ??????code:?200,
          ??????message:?'Success.'
          ????};
          ??}

          ??//?DELETE?/user/:_id
          ??@Delete(':_id')
          ??async?deleteOne(@Param('_id')?_id:?string):?Promise?{
          ????await?this.userService.deleteOne(_id);
          ????return?{
          ??????code:?200,
          ??????message:?'Success.'
          ????};
          ??}
          }

          至此,我們就完成了一個完整的 CRUD 操作,接下來我們來測試一下~

          接口測試

          接口測試我們用的是 Postman,大家可以去下載一個,非常好用的接口自測工具。

          數(shù)據(jù)庫可視化工具我們用的是 MongoDB 官方的 MongoDB Compass,也很不錯。

          GET /user/users

          GET /user/users

          一開始我們的數(shù)據(jù)庫中什么都沒有,所以返回了一個空數(shù)組,沒用用戶信息。

          POST /user

          POST /user

          現(xiàn)在我們添加一條用戶信息,服務(wù)器返回添加成功。

          Added

          GET /user/:_id

          GET /user/:_id

          添加完一條用戶信息之后再查詢,可算是能查詢到我的信息了。

          PUT /user/:_id

          PUT /user/:_id

          現(xiàn)在假如我想修改密碼,發(fā)送一個 PUT 請求。

          Edited

          DELETE /user/:_id

          DELETE /user/:_id

          現(xiàn)在我們刪除一下剛才添加的用戶信息。

          Deleted

          會發(fā)現(xiàn)數(shù)據(jù)庫中的內(nèi)容已經(jīng)被刪除了。

          完結(jié)撒花

          大功告成,CRUD 就這么簡單,用這個項目去參加一些學校舉行的比賽,拿個獎肯定沒什么問題,開箱即用(學校老師們別打我)。

          總結(jié)

          教程還算是用了比較通俗易懂的方式為大家講解了如何寫一個帶有 CRUD 功能的后端 Node.js 應(yīng)用,框架采用的是 Nest.js。

          相信大家在上面的教程中肯定有非常多不懂的部分,比如說 @Get()、@Post()@Param()@Body() 等等的裝飾器,再比如說一些 Nest.js 相關(guān)的概念。

          沒關(guān)系,我的建議是:「學編程先模仿,遇到不懂的地方先記住,等到自己的積累夠多了,總有一天你會回過頭發(fā)現(xiàn)自己茅塞頓開,突然懂了」。這也是我個人學習的一個小技巧。

          在學習的過程中,也一定會遇到一些問題,學習編程的過程中遇到問題不能自己憋著,「一定要學會請教大佬!一定要學會請教大佬!一定要學會請教大佬」!重要的事情說三遍。

          不過也別很簡單的問題就去請教大佬,而且最好給一點小小的報酬,畢竟誰也沒有義務(wù)幫你解決問題。

          我在學習的過程中也請教了一些社區(qū)里面的大佬,同時還進入了 Nest.js 的社區(qū)答疑群,向國外友人請教學到了不少知識。

          當然,這個 Demo 中也有很多可以完善的地方,比如說「錯誤處理」

          數(shù)據(jù)庫的操作肯定是有可能出現(xiàn)錯誤的,比如說我們漏傳了 required: true 的參數(shù),數(shù)據(jù)庫就會報錯。

          這個時候我們就要寫一個 try/catch 捕獲這個異常,或者干脆寫一個異常的過濾器,將所有的異常統(tǒng)一處理(Nest.js 支持過濾器)

          除此之外,既然有可能出現(xiàn)異常,那么我們就需要一個日志系統(tǒng)去捕獲這個異常,方便查錯糾錯。

          如果涉及到登錄注冊的部分,還有密碼加解密的過程,同時還可能有權(quán)限校驗問題需要進行處理。

          所以后端的同學肯定不止 CRUD 啦(可算圓回來了)。

          這個教程的所有代碼我都放在了我的 GitHub 倉庫:Nest-CRUD-Demo[7],歡迎大家點個 Star!

          參考資料

          • NestJS - A progressive Node.js framework[8]
          • Nest.js 中文文檔[9]

          Reference

          [1]

          Nest-CRUD-Demo: https://github.com/wjq990112/Nest-CRUD-Demo

          [2]

          GitHub Repo: https://github.com/nestjs/nest

          [3]

          《走近 MidwayJS :初識 TS 裝飾器與 IoC 機制》: https://juejin.im/post/6859314697204662279

          [4]

          Midway 官網(wǎng): https://midwayjs.org/midway/

          [5]

          MongoDB 官網(wǎng): https://mongodb.com/download-center/community

          [6]

          點擊這里: https://brew.sh/

          [7]

          Nest-CRUD-Demo: https://github.com/wjq990112/Nest-CRUD-Demo

          [8]

          NestJS - A progressive Node.js framework: https://nestjs.com/

          [9]

          Nest.js 中文文檔: https://docs.nestjs.cn/

          瀏覽 58
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91福利在线看 | 国产精品秘 入口免费直播大尺度 | 狼友导航 | 俺来也俺也去 | 亚洲欧美秘 无码一区二区蜜桃 |