Nest.js 從零到壹系列(一):項目建立&路由設置&模塊

前言

本系列將之前端的視角進行書寫,分享本身的踩坑經歷。教程主要面向前端或者毫無後端經驗,可是又想嘗試 Node.js 的讀者,固然,也歡迎後端大佬斧正。前端

Nest 是一個用於構建高效,可擴展的 Node.js 服務器端應用程序的框架。它使用漸進式 JavaScript,內置並徹底支持 TypeScript(但仍然容許開發人員使用純 JavaScript 編寫代碼)並結合了 OOP(面向對象編程),FP(函數式編程)和 FRP(函數式響應編程)的元素。node

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

Nest 是我近半年接觸的一款後端框架,以前接觸的是 Koa2,但由於老項目被「資深」前端寫的亂七八糟,因此我就選擇了這款以 TypeScript 爲主的、最近在國內興起的框架重構了。截止目前,Github 上的 nestjs 擁有 25.2k 個 Star,主要用戶在國外,因此側面能夠證實其必定的穩定性。數據庫

Nest 採用 MVC 的設計模式,若是有 Angular 項目經驗的讀者,應該會以爲熟悉。我沒寫過 Angular,因此當初學的時候,走了一些彎路,主要是接受這種類 Spring 的設計理念。npm

好了,碎碎唸到此爲止,開始吧:編程

1、項目建立

項目環境:json

  • node.js: 11.13.0+
  • npm: 6.7.0+
  • nestjs: 6.0.0
  • typescript: 3.8.3

先確操做系統上安裝了 Node.js(>= 8.9.0),而後安裝 Nest.js,而後新建項目,輸入以下指令:bootstrap

$ npm i -g @nestjs/cli
$ nest new project-name
複製代碼

輸入完後,會初始化,此時,會問你使用哪種方式來管理依賴包:後端

我選擇的是 yarn,主要是國內的 npm 下載得比較慢。若是沒有 yarn 的,能夠下載一個,也可使用 npm,不過本系列教程都使用 yarn設計模式

等雞啄完了米,等狗舔完了面,等火燒斷了鎖,就會獲得下列信息:

按照提示,進入項目,不出意外,目錄應該是這個樣子的:

運行 yarn run startyarn start,會看到控制檯輸出以下信息,表示服務已啓動:

2、Hello World!

1. 路由指向

打開 src 下的 main.ts,不出意外,應該會看到下列代碼:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

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

await NestFactory.create(AppModule); 表示使用 Nest 的工廠函數建立了 AppModule,關於 Module 稍後會介紹。

await app.listen(3000) 表示監聽的是 3000 端口,這個能夠自定義。若 3000 端口被佔用致使項目啓動失敗,能夠修改爲其餘端口。

而後咱們經過 Postman 訪問本地的3000端口,會發現出現以下信息:

而後咱們須要作的就是,找到爲何會出現 Hello World! 的緣由。

打開 src 下的 app.service.ts,會看到以下代碼:

// src/app.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}
複製代碼

發現這裏有個方法 getHello(),返回了 Hello World! 字符串,那麼它在哪裏被調用呢?

打開 src 下的 app.controller.ts

// src/app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}
複製代碼

喔,原來如此,這裏引入了 app.service.ts 中的 AppService 類,並實例化,而後經過 @Get() 修飾 AppController 裏的 getHello() 方法,表示這個方法會被 GET 請求調用。

咱們修改一下路由,就是在 @Get() 括號裏面寫上字符串:

// src/app.controller.ts
@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get('hello-world')
  getHello(): string {
    return this.appService.getHello();
  }
}
複製代碼

而後重啓項目(在控制檯按下 Ctrl + C 終止項目,而後再輸入 yarn start),此時咱們再訪問 localhost:3000/,就會發現 404 了:

此時,咱們輸入 localhost:3000/hello-world,熟悉的字符出現了:

這就是 Nest 的路由,是否是很簡單?

2. 局部路由前綴

路由還能夠設置局部和全局的前綴,使用前綴能夠避免在全部路由共享通用前綴時出現衝突的狀況。

仍是 app.controller.ts,在 @Controller()寫入 lesson-1,這樣的話就表示當前文件中,全部的路由都有了前綴 lesson-1

// src/app.controller.ts
@Controller('lesson-1')
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get('hello-world')
  getHello(): string {
    return this.appService.getHello();
  }
}
複製代碼

重啓項目,此時咱們訪問 localhost:3000/lesson-1/hello-world,就會指向 getHello() 方法了:

3. 全局路由前綴

這個更簡單了,只須要在 main.ts 中加上app.setGlobalPrefix()

// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.setGlobalPrefix('nest-zero-to-one'); // 全局路由前綴
  await app.listen(3000);
}
bootstrap();
複製代碼

以後只要請求服務,全部的路由都要加上 nest-zero-to-one 前綴:

4. 使用 nodemon 模式啓動項目

若是不想頻繁重啓,可使用 yarn start:dev 啓動項目,它會使用 nodemon 監聽文件的變化,並自動重啓服務。

若是出現下列信息:

緣由是可能以前裝過 typescript 或者 nestjs 腳手架,而後新建項目的時候,typescript 版本比較舊,只需在項目中更新到 3.7.0 以上:

$ yarn add typescript -D
複製代碼

出現這個截圖,可是沒有路由信息,表示 nodemon 的配置須要更改:

package.json:
❌ "start:dev": "concurrently --handle-input \"wait-on dist/main.js && nodemon\" \"tsc -w -p tsconfig.build.json\" ",    
✅ "start:dev": "concurrently --handle-input \"wait-on dist/src/main.js && nodemon\" \"tsc -w -p tsconfig.build.json\" ",

nodemon.json:
❌ "exec": "node dist/main""exec": "node dist/src/main"
複製代碼

而後再運行 yarn start:dev 就能夠了:

或者乾脆直接把 main.ts 扔到根目錄去(和src同級)

這樣再改動什麼文件,都會自動重啓服務了。

3、新增模塊

經過上文,應該熟悉了 NestJS 的設計模式,主要就是 ControllerServiceModule 共同努力,造成了一個模塊。

  • Controller:傳統意義上的控制器,提供 api 接口,負責處理路由、中轉、驗證等一些簡潔的業務;
  • Service:又稱爲 Provider, 是一系列服務、repo、工廠方法、helper 的總稱,主要負責處理具體的業務,如數據庫的增刪改查、事務、併發等邏輯代碼;
  • Module:負責將 ControllerService 鏈接起來,相似於 namespace 的概念;

很直觀的傳統 MVC 結構,有 Spring 開發經驗的後端應該不會陌生。

下面咱們經過新增一個 User 模塊來進行實戰:

1. Service

我的習慣先建立 Service,最後再建立 Module,由於 Controller 和 Module 都須要引入 Service,這樣引入的時候就能夠有提示了(固然,也能夠事先寫 import 語句,但 ESLint 的檢查會冒紅點,強迫症患者表示不接受)。

使用 nest-cli 提供的指令能夠快速建立文件,語法以下:

$ nest g [文件類型] [文件名] [文件目錄(src目錄下)]
複製代碼

咱們輸入:

$ nest g service user logical
複製代碼

就會發現 src 目錄下多了 logical/user/ 文件夾(我的喜歡將業務邏輯相關的文件放入 logical)

上圖中的 user.service.spec.ts 能夠不用管……至少我寫了大半年,也沒動過這種文件。

而後咱們看一下 user.service.ts,用指令建立的文件,基本都長這樣:

// src/logical/user/user.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class UserService {}

複製代碼

因而,咱們能夠仿照 app.service.ts 來寫一個簡單的業務了:

// src/logical/user/user.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class UserService {
    findOne(username: string): string {
    if (username === 'Kid') {
      return 'Kid is here';
    }
    return 'No one here';
  }
}
複製代碼

2. Controller

如今,咱們來寫控制器,輸入下列命令:

$ nest g controller user logical
複製代碼

初始化的 Controller 基本都長這個樣:

// src/logical/user/user.controller.ts
import { Controller } from '@nestjs/common';

@Controller('user')
export class UserController {}
複製代碼

接下來,咱們把 Service 的業務邏輯引入進來:

import { Controller, Post, Body } from '@nestjs/common';
import { UserService } from './user.service';

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

  @Post('find-one')
  findOne(@Body() body: any) {
    return this.usersService.findOne(body.username);
  }
}
複製代碼

須要先用構造器實例化,而後才能調用方法,這裏使用的是 POST 來接收請求,經過 @Body() 來獲取請求體(request.body)的參數。

咱們用 Postman 來測試一下,先隨意傳入一個 username:

再傳入 'Kid':

由此可知,咱們成功匹配到了路由,而且編寫的業務生效了。

至此 70% 的流程已經走完,之後開發業務(搬磚),基本都是在 Service 和 Controller 裏面折騰了。。。

注意:千萬不要往 Controller 裏面添加亂七八糟的東西,尤爲不要在裏面寫業務邏輯,Controller 就應該保持簡潔、乾淨。不少前端剛寫 Node 的時候,都喜歡在這裏面寫邏輯,只爲了省事,卻不知這對後期的維護是個災難。

3. Module

這個是鏈接 Service 和 Controller 的東東,不少人會奇怪,上文只是建立了 Service 和 Controller,怎麼就能夠訪問了呢?

打開 app.module.ts:

// src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserService } from './logical/user/user.service';
import { UserController } from './logical/user/user.controller';

@Module({
  imports: [],
  controllers: [AppController, UserController],
  providers: [AppService, UserService],
})
export class AppModule {}
複製代碼

發現使用指令建立文件的時候,已經自動幫咱們引入 User 相關文件了,而 main.ts 文件裏,又已經引入了 AppModule,並使用 NestFactory 建立了實例。

所以,若是是新建無關痛癢的子模塊,即便不新建 Module 文件,也能經過路由訪問。

可是做爲教程,仍是大體說一下吧,先建立文件:

$ nest g module user logical
複製代碼

初始化的 Module 基本都長這個樣:

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

@Module({})
export class UserModule {}
複製代碼

咱們把 Service 和 Controller 組裝起來:

import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';

@Module({
  controllers: [UserController],
  providers: [UserService],
  exports: [UserService],
})
export class UserModule {}
複製代碼

這樣作有什麼好處呢,就是其餘 Module 想引入 User 的時候,就不用同時引入 Service 和 Controller 了,咱們修改一下 app.module.ts

// src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
// import { UserService } from './logical/user/user.service';
// import { UserController } from './logical/user/user.controller';
import { UserModule } from './logical/user/user.module';

@Module({
  imports: [UserModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
複製代碼

保存運行,發現路由依然生效:

固然,Module 還有其餘高級玩法,這個就不在這裏展開了。

總結

本篇介紹了 Nest.js 項目的建立,路由的訪問,以及如何新增模塊。

每一個模塊又可分爲 Service、Controller、Module。在本篇中:Service 負責處理邏輯、Controller 負責路由、Module 負責整合。

經過實戰能夠看出,Nest 仍是相對簡單的,惟一的障礙可能就是 TypeScript 了。

寫慣了 JavaScript 的人,可能不是很能適應這種類型檢查,尤爲是熱衷於使用各類騷操做的,不過既然涉及到了後端領域,仍是嚴謹一點比較好,前期能夠避免各類不規範致使的坑。

下一篇將介紹如何鏈接 MySQL 數據庫。

相關文章
相關標籤/搜索