跟随官网学nestjs之入门

起步

Nest 是一个用于构建高效,可扩展的 Node.js 服务器端应用程序的框架。

安装

可以使用官方提供的 cli 构建项目,也可以克隆启动项目

  • 使用 cli 安装
 npm i -g @nestjs/cli
 nest new nest-demo

这个时候就已经初始化好了一个入门项目,打开项目,可以看到项目基本配置已经齐全,包含了 nest 核心文件

src
├── app.controller.ts // 带有单个路由的基本控制器示例。
├── app.module.ts // 应用程序的根模块。
└── main.ts // 应用程序入口文件。它使用 NestFactory 用来创建 Nest 应用实例。
  • 运行项目
yarn start

访问 http://localhost:3000,就可以看到 Hello World!。最简单的架子就已经好了,现在就是学习 nest 它提供的方法api

控制器

控制器负责处理传入的 请求 和向客户端返回 响应 ,就是我们所说的 controller 层。

路由

nest 实现路由,需要用到 @Controller() 装饰器,可以为其设置前缀,使用路径前缀可以对一组相关的路由进行分组,接下来将会用一个例子 user 模块完成

  • 使用 cli 内置命令创建一个 controller
nest g controller user

这时就会看到项目里已经有了 user 文件

import { Controller, Get } from '@nestjs/common';

@Controller('user')
export class UserController {

  @Get()
  findOne(): string {
    return 'this is user controller';
  }
}

其中 @Get() 是一个装饰器,告诉我们这个方法是 HTTPget 请求,那么我们参数如何传递,这时就需要用到其他的装饰器。
我们重启一下服务,在浏览器输入 http://localhost:3000/user,就能看到页面显示的 this is user controller。我们也可以使用 yarn start:dev 命令来热更新,就不用每次更改代码后重启服务。

路由参数

nest 提供 @Param()@Body() 等等装饰器,我们改造一下刚刚,提供一个根据 id 查询用户的接口

import { Controller, Get, Param } from '@nestjs/common';

@Controller('user')
export class UserController {

  @Get(':id')
  findOne(@Param('id') id: string): string {
    return `find a user by id = ${id}`;
  }
}

在浏览器输入 http://localhost:3000/user/1,就能看到 find a user by id = 1。这就是使用路由 params 参数,当然使用 query 也是可以的;我们增加一个查询列表的接口

  @Get()
  findAll(
    @Query('pageIndex') pageIndex: number,
    @Query('pageSize') pageSize: number,
  ): string {
    return `find user list, pageIndex = ${pageIndex}, pageSize = ${pageSize}`;
  }

这时在浏览器输入 http://localhost:3000/user?pageSize=10&pageIndex=1,就会看见 find user list, pageIndex = 1, pageSize = 10

API 文档

OpenAPI(Swagger)规范是一种用于描述 RESTful API 的强大定义格式。 Nest 提供了一个专用模块来使用它。

安装

$ npm install --save @nestjs/swagger swagger-ui-express

引导

安装过程完成后,打开根目录文件 main.ts,并使用 SwaggerModule 类初始化 Swagger

import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const options = new DocumentBuilder()
    .setTitle('nest demo example')
    .setDescription('The nest demo API description')
    .setVersion('1.0')
    .build();
  const document = SwaggerModule.createDocument(app, options);

  SwaggerModule.setup('api-docs', app, document);

  await app.listen(3000);
}
bootstrap();

这时访问 http://localhost:3000/api-docs/ 就能看见如下的 Swagger UI 界面:

img

我们删除项目中 app 模块多余的代码,为当前 user 模块打上标签

删除 app.service.tsapp.controller.ts,修改 app.module.ts

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

@Module({
  imports: [],
  controllers: [UserController],
  providers: [],
})
export class AppModule {}

user.controller.ts 加上 ApiTigs 告诉查看文档的人这个user模块,nestjs/swagger 提供了一系列内置注解方法,可以给接口添加更多的描述,便于使用者能更好的阅读理解接口

import { Controller, Get, Param, Query } from '@nestjs/common';
import { ApiTags, ApiOperation } from '@nestjs/swagger';

@ApiTags('user')
@Controller('user')
export class UserController {

  @ApiOperation({
    summary: 'find a user by id',
  })
  @Get(':id')
  findOne(@Param('id') id: string): string {
    return `find a user by id = ${id}`;
  }

  @ApiOperation({
    summary: 'find user list',
  })
  @Get()
  findAll(
    @Query('pageIndex') pageIndex: number,
    @Query('pageSize') pageSize: number,
  ): string {
    return `find user list, pageIndex = ${pageIndex}, pageSize = ${pageSize}`;
  }
}

这时刷新浏览器,就能看到文档更新了

img

swagger 的好处就是我们可以使用它的 try it out 来模拟真实发起请求,也能看到请求需要的参数以及请求返回的数据,比 postman 更方便,而且我们还可以写上参数示例。

SwaggerModule 在路由处理程序中查找所有使用的 @Body()@Query()@Param() 装饰器来生成 API 文档。该模块利用反射创建相应的模型定义,比如我们新增一个创建用户的接口

新建一个 dto

import { ApiProperty } from '@nestjs/swagger';

export class CreateUserDto {
  @ApiProperty({ example: 'username' })
  readonly username: string;

  @ApiProperty({ example: 18 })
  readonly age: number;

  @ApiProperty({ example: 1 })
  readonly sex: number;

  @ApiProperty({ example: 'address' })
  readonly address: string;
}

user.controller.ts 新增创建接口

...
  @ApiOperation({
    summary: 'create a user',
  })
  @Post()
  create(
    @Body() createUserDto: CreateUserDto,
  ): string {
    return `create user, the user name = ${createUserDto.username}`;
  }
...

刷新接口文档,就能看见新增了一个接口,基于 CreateUserDto 将创建模块定义:

img

其中我们使用 ApiProperty 装饰器来说明改字段的属性,更多用法可以查阅官方文档

数据库

官方提供几种数据库模型,这里我使用的是 Sequelize

Sequelize 是一个用普通 JavaScript 编写的流行对象关系映射器( ORM ),但是有一个 Sequelize-TypeScript 包装器,它为基本 Sequelize 提供了一组装饰器和其他附加功能。

安装

$ npm install --save sequelize sequelize-typescript mysql2 @nestjs/sequelize
$ npm install --save-dev @types/sequelize

模型注入

Sequelize 中,模型在数据库中定义了一个表。该类的实例表示数据库行。首先,我们至少需要一个实体 user.model.ts

import {
  Column,
  Model,
  Table,
  TableOptions,
  DataType,
  CreatedAt,
  UpdatedAt,
} from 'sequelize-typescript';

const tableOptions: TableOptions = {
  tableName: 'user',
};

@Table(tableOptions)
export class User extends Model<User> {
  @Column({
    type: DataType.BIGINT,
    allowNull: false,
    autoIncrement: true,
    primaryKey: true,
  })
  public id: number;

  @Column({
    type: DataType.STRING,
    allowNull: false,
  })
  username: string;

  @Column({
    type: DataType.STRING,
    allowNull: true,
  })
  password: string;

  @Column({
    type: DataType.STRING,
    allowNull: false,
  })
  address: string;

  @Column({
    type: DataType.BIGINT,
    allowNull: false,
  })
  age: number;

  @Column({
    type: DataType.BIGINT,
    allowNull: false,
  })
  sex: number;

  @CreatedAt public createdAt: Date;

  @UpdatedAt public updatedAt: Date;
}

该实体的定义,对应的就是映射到数据库的模型。新建 user.module.ts 来注入该模型

import { Module } from '@nestjs/common';
import { SequelizeModule } from '@nestjs/sequelize';
import { UserController } from './user.controller';
import { User } from './user.model';

@Module({
  imports: [
    SequelizeModule.forFeature([User]),
  ],
  providers: [],
  controllers: [UserController],
  exports: [],
})
export class UserModule {}

连接数据库

我们在根节点实例化,并连接数据库

import { Module } from '@nestjs/common';
import { SequelizeModule } from '@nestjs/sequelize';
import { UserModule } from './user/user.module';
import { User } from './user/user.model';
import { UserController } from './user/user.controller';

@Module({
  imports: [
    UserModule,
    SequelizeModule.forRoot({
      dialect: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'root',
      database: 'nest_demo',
      models: [User],
    }),
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

我们创建服务层,新建 user.service.ts

import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/sequelize';
import { User } from './user.model';
import { CreateUserDto } from './dto';

@Injectable()
export class UserService {

  constructor(
    @InjectModel(User)
    private userModel: typeof User,
  ) {}

  async create(createUserDto: CreateUserDto): Promise<User> {
    const user = new User();
    user.username = createUserDto.username;
    user.age = createUserDto.age;
    user.address = createUserDto.address;
    user.sex = createUserDto.sex;

    return await user.save();
  }

  async findAll(): Promise<User[]> {
    return await this.userModel.findAll<User>({
      attributes: ['id', 'username', 'address', 'age', 'sex'],
    });
  }

  async findOneById(id: string): Promise<User> {
    return await this.userModel.findOne<User>({
      where: { id },
      attributes: ['id', 'username', 'address', 'age', 'sex'],
    });
  }
}

然后修改 controller

import { Controller, Get, Param, Query, Post, Body } from '@nestjs/common';
import { ApiTags, ApiOperation } from '@nestjs/swagger';
import { CreateUserDto } from './dto';
import { UserService } from './user.service';
import { User } from './user.model';

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

  @ApiOperation({
    summary: 'find a user by id',
  })
  @Get(':id')
  findOne(@Param('id') id: string): Promise<User> {
    return this.userService.findOneById(id);
  }

  @ApiOperation({
    summary: 'find user list',
  })
  @Get()
  findAll(): Promise<User[]> {
    return this.userService.findAll();
  }

  @ApiOperation({
    summary: 'create a user',
  })
  @Post()
  create(
    @Body() createUserDto: CreateUserDto,
  ): Promise<User> {
    return this.userService.create(createUserDto);
  }
}

这时访问文档,我们执行一下查询接口,就可以看到返回的数据结果了

img

到此,Nest 入门已经结束了,接下来我们会介绍简单的守卫

代码传送门:nest-demo

参考资料:Nest 文档

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容