小苏学习Nest第一章

497 阅读6分钟

Nest-1

next是一款用JS开发的全栈框架,它是基于express框架基础上开发而成,可以用react写客户端,node.js写服务端。一份代码可在前后端同时运行,这在next中称之为同构!

一. 项目创建

先确操作系统上安装了 Node.js(>= 10.13.0),然后安装 Nest.js,然后新建项目,输入如下指令:

npm i -g @nestjs/cli
nest new ‘项目名’

然后选依赖管理器,npm ,yarn ,pnpm。 咱们研究就是用最新的。直接pnpm。

鸡啄完了米,等狗舔完了面,等火烧断了锁项目就创建完了

然后启动npm start启动即可

二 . Hellow World!

打开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 端口被占用导致项目启动失败,可以修改成其他端口。

然后我们访问本地的3000端口,会发现出现如下信息:

然后我们需要做的就是,找到为什么会出现 Hello World! 的原因。

打开 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() 括号里面写上字符串:

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
​
@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}
​
  @Get('hellowWorld')
  getHello(): string {
    return this.appService.getHello();
  }
}
​

然后重启项目(在控制台按下 Ctrl + C 终止项目,然后再输入 yarn start),此时我们再访问 localhost:3000/,就会发现 404 了:

此时,我们输入 localhost:3000/helloWorld,熟悉的字符出现了:

这就是 Nest 的路由,是不是很简单?

2. 局部路由前缀

路由还可以设置局部和全局的前缀,使用前缀可以避免在所有路由共享通用前缀时出现冲突的情况。

还是 app.controller.ts,在 @Controller()写入 ssf,这样的话就表示当前文件中,所有的路由都有了前缀 ssf

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('ssfBaseUrl'); // 全局路由前缀
  await app.listen(3000);
}
bootstrap();

之后只要请求服务,所有的路由都要加上 ssfBaseUrl 前缀:

4. 使用 nodemon 模式启动项目

如果不想频繁重启,可以使用 yarn start:dev 启动项目,它会使用 nodemon 监听文件的变化,并自动重启服务。

如果出现下列信息:这边尝试使用 npm start:dev 识别不出来,但是使用yarn start:dev直接成功了

可能出现的报错(别人碰到了,我尝试的时候没碰见)

原因是可能之前装过 typescript 或者 nestjs 脚手架,然后新建项目的时候,typescript 版本比较旧,重装一下就行了。

三.新增模块

通过上文,应该熟悉了 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.ts,用指令创建的文件,基本都长这样:

import { Injectable } from '@nestjs/common';
​
@Injectable()
export class UserService {}

于是,我们可以仿照 app.service.ts 来写一个简单的业务了:

import { Injectable } from '@nestjs/common';
​
@Injectable()
export class UserService {
  findUser(username: string): string {
    if (username === 'ssf') {
      return 'yyds';
    }
    return '不认识';
  }
}

2. Controller

现在,我们来写控制器,输入下列命令:

nest g controller user logical

初始化的 Controller 基本长这样

import { Controller } from '@nestjs/common';
@Controller('user')
export class UserController {}

然后把Service的业务逻辑引进来:

import { Body, Controller, Post } from '@nestjs/common';
import { UserService } from './user.service';
​
@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}
​
  @Post('findUser')
  findUser(@Body() body: any) {
    return this.userService.findUser(body.username);
  }
}

需要先用构造器实例化,然后才能调用方法,这里使用的是 POST 来接收请求,通过 @Body() 来获取请求体(request.body)的参数。

3. Module

这个是连接 Service 和 Controller 的东东,很多人会奇怪,上文只是创建了 Service 和 Controller,怎么就可以访问了呢?

打开 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 { SuperdemoService } from './logical/superdemo/superdemo.service';
import { UserController } from './logical/user/user.controller';
​
@Module({
  imports: [],
  controllers: [AppController, UserController],
  providers: [AppService, UserService, SuperdemoService],
})
export class AppModule {}

发现使用指令创建文件的时候,已经自动帮我们引入 User 相关文件了,而 main.ts 文件里,又已经引入了 AppModule,并使用 NestFactory 创建了实例。

因此,如果是新建无关痛痒的子模块,即使不新建 Module 文件,也能通过路由访问。

但是都这样的话,会显的AppModule很臃肿,虽然可以,但是我们尽量还是模块区分一下会感觉舒服一些。

nest g module user logical

初始化的Module长这个样子

import { Module } from '@nestjs/common';
@Module({})
export class UserModule {}

这样做有什么好处呢,就是其他 Module 想引入 User 的时候,就不用同时引入 Service 和 Controller 了,我们修改一下 app.module.ts:因为这个时候只需要用module就可以了

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { SuperdemoService } from './logical/superdemo/superdemo.service';
import { UserModule } from './logical/user/user.module';
​
@Module({
  imports: [UserModule],
  controllers: [AppController],
  providers: [AppService, SuperdemoService],
})
export class AppModule {}
​

然后再调用一下user中写的接口,仍然可以完美运行。当然,Module 还有其他高级玩法,这个就不在这里展开了。

四. 总结

本篇介绍了 Nest.js 项目的创建,路由的访问,以及如何新增模块。

每个模块又可分为 Service、Controller、Module。在本篇中:Service 负责处理逻辑、Controller 负责路由、Module 负责整合。

通过实战可以看出,Nest 还是相对简单的,唯一的障碍可能就是 TypeScript 了。

写惯了 JavaScript 的人,可能不是很能适应这种类型检查,尤其是热衷于使用各种骚操作的,不过既然涉及到了后端领域,还是严谨一点比较好,前期可以避免各种不规范导致的坑。

End

本节完,下一篇将介绍如何连接 MySQL 数据库。