本系列将之前端的视角进行书写,分享本身的踩坑经历。教程主要面向前端或者毫无后端经验,可是又想尝试 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
好了,碎碎念到此为止,开始吧:编程
项目环境:json
先确操做系统上安装了 Node.js(>= 8.9.0),而后安装 Nest.js,而后新建项目,输入以下指令:bootstrap
$ npm i -g @nestjs/cli
$ nest new project-name
复制代码
输入完后,会初始化,此时,会问你使用哪种方式来管理依赖包:后端
我选择的是 yarn
,主要是国内的 npm
下载得比较慢。若是没有 yarn
的,能够下载一个,也可使用 npm
,不过本系列教程都使用 yarn
。设计模式
等鸡啄完了米,等狗舔完了面,等火烧断了锁,就会获得下列信息:
按照提示,进入项目,不出意外,目录应该是这个样子的:
运行 yarn run start
或 yarn start
,会看到控制台输出以下信息,表示服务已启动:
打开 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 的路由,是否是很简单?
路由还能够设置局部和全局的前缀,使用前缀能够避免在全部路由共享通用前缀时出现冲突的状况。
仍是 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()
方法了:
这个更简单了,只须要在 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
前缀:
若是不想频繁重启,可使用 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同级)
这样再改动什么文件,都会自动重启服务了。
经过上文,应该熟悉了 NestJS 的设计模式,主要就是 Controller
、Service
、Module
共同努力,造成了一个模块。
Controller
:传统意义上的控制器,提供 api 接口,负责处理路由、中转、验证等一些简洁的业务;Service
:又称为 Provider
, 是一系列服务、repo、工厂方法、helper 的总称,主要负责处理具体的业务,如数据库的增删改查、事务、并发等逻辑代码;Module
:负责将 Controller
和 Service
链接起来,相似于 namespace
的概念;很直观的传统 MVC 结构,有 Spring 开发经验的后端应该不会陌生。
下面咱们经过新增一个 User 模块来进行实战:
我的习惯先建立 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';
}
}
复制代码
如今,咱们来写控制器,输入下列命令:
$ 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 的时候,都喜欢在这里面写逻辑,只为了省事,却不知这对后期的维护是个灾难。
这个是链接 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 数据库。