前端所有代码已经同步到 GitHub Repo: EthanLuu/soomooc: React + TypeScript 实践,在线教学直播平台。 (github.com)
一直用 json-server 来 mock 数据显然是不够的,要想实现聊天室和其他的功能,还是需要简单开发一下后端接口的。

直接跳过对 nest.js 的详细介绍,安装过程可以直接参考官网,这里就记录一下纯粹的 CRUD 接口的完整开发过程。

创建 Module

Nest.js 要实现一个数据结构的 CRUD,主要需要三个东西:Controller, Service, ModuleModule 起到的作用是连接 ServiceController

新建一个组件只需要在项目的根目录下直接用命令行输入对应的命令即可。

1
nest g service menu server

这是框架为我们自动创建的文件。

image-20210531180138237

创建 Service

其中的 Service 存放的具体的增删改查的逻辑和操作。

image-20210531180218180

只需要对 menu.service.ts 进行修改,以下是刚新建完的样子:

1
2
3
4
import { Injectable } from '@nestjs/common';

@Injectable()
export class MenuService {}

创建 Controller

Controller 在整个项目中负责的是根据请求的方法参数去调用 Service 中的方法。

一样是使用命令行直接创建文件即可。

1
nest g controller menu server

image-20210531180203679

初始化的 controller 长这个样子:

1
2
3
4
import { Controller } from '@nestjs/common';

@Controller('menu')
export class MenuController {}

CRUD

终于要开始增删改查了,激不激动!

不过先等等,为了连接 MongoDB,需要确保安装了 mongoose 依赖和进行了相关的配置。

1
2
yarn add mongoose @nestjs/mongoose
yarn add @types/mongoose -D # ts支持

在根模块 app.module.ts 中要引入该依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './server/user/user.module';
import { MongooseModule } from '@nestjs/mongoose';
import { CourseModule } from './server/course/course.module';
import { MenuModule } from './server/menu/menu.module';
@Module({
imports: [
MongooseModule.forRoot('mongodb://admin:xxxx@121.xxx.xxx.xxx:27017'), // 服务器上的 MongoDB 的连接地址
UserModule, // 创建完 module 后自动引入的
CourseModule,
MenuModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

在我们写的 menu 的模块中也需要对数据库进行配置。

首先需要数据结构进行定义,每一个菜单项包括『方向』和『子类别(数组)』。

1
2
3
4
5
6
7
8
9
// src/server/menu/menu.interface.ts
import { Document } from 'mongoose';

export interface Menu extends Document {
readonly _id: string;
readonly direction: string;
readonly types: string[];
}

其次,还需要新建一个 src/server/menu/menu.schema.ts 描述我们数据库。

1
2
3
4
5
6
7
8
9
10
11
12
13
import { Schema } from 'mongoose';

export const menuScheme = new Schema({
direction: { type: String, required: true },
types: {
type: Array,
required: true,
items: {
type: String,
},
},
});

再去对应的 module 中引入类型的定义和 mongoose

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// src/server/menu/menu.module.ts
import { menuScheme } from './menu.schema';
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { MenuController } from './menu.controller';
import { MenuService } from './menu.service';

@Module({
imports: [
MongooseModule.forFeature([{ name: 'Menus', schema: menuScheme }]),
],
controllers: [MenuController],
providers: [MenuService]
})
export class MenuModule {}

OK,接下去在 service 中修改一下获取菜单信息的逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Menu } from './menu.interface';

@Injectable()
export class MenuService {
constructor(@InjectModel('Menus') private readonly menuModel: Model<Menu>) {} // 绑定模型

async findAll(): Promise<Menu[]> {
const menus = await this.menuModel.find();
return menus;
}
}

定义完了这个逻辑之后,为了调用这个方法,我们需要在 controller 中绑定路由。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { MenuService } from './menu.service';
import { Controller, Get } from '@nestjs/common';
import { Menu } from './menu.interface';

interface MenuResponse<T> {
code: number;
data?: T;
message: string;
}

@Controller('menu')
export class MenuController {
constructor(private readonly menuService: MenuService) {}

@Get('menus')
async findAll(): Promise<MenuResponse<Menu[]>> {
return {
code: 200,
data: await this.menuService.findAll(),
message: 'Success.',
};
}
}

在这里我们只有一个 Get 方法,用于查询数据库中的所有菜单。

我们用 Postman 测试一下。

老样子,要记得在命令行运行 yarn start:dev 打开服务器,我设置的端口是 3333

GET 请求地址 http://localhost:3333/menu/menus,成功返回一个包括 code, data, message 在内的代码。

image-20210531191446258

因为数据库里是空的,所以自然返回的是空数组。

接下去写一个用 POST 请求添加菜单的方法,首先定义一下请求的 BODY 格式,新建 src/server/menu/menu.dto.ts

1
2
3
4
5
6
export class CreateMenuDTO {
readonly _id: string;
readonly direction: string;
readonly types: string[];
}

这里的 DTO 指的是数据传输对象 Data Transfer Object

然后往 ServiceController 里添加方法。

1
2
3
4
5
// src/server/menu/menu.service.ts
// 添加单个菜单
async addOne(body: CreateMenuDTO): Promise<void> {
await this.menuModel.create(body);
}
1
2
3
4
5
6
7
8
9
10
// src/server/menu/menu.controller.ts
// POST /menu
@Post()
async addOne(@Body() body: CreateMenuDTO): Promise<MenuResponse> {
await this.menuService.addOne(body);
return {
code: 200,
message: 'Success.',
};
}

在这之后,通过 POST 请求将原来的 mock 数据写入到数据库中。

当前写的几个接口和原来的 mock 方式都有一些出入,包括返回的数据格式等等。这些调整需要手动在 React 项目中进行。

Thumbnail (2)