项目背景和要求
背景
之前看过政采云的前端周刊的介绍,想着用我们公司的技术栈nestjs/nextjs撸个简化版,这类小项目也非常适合新框架新技术的尝试和使用。
在这里正好把开发好的项目给大家分享下,如果你正在学习nestjs/nextjs ,缺乏demo项目,可以尝试实现一波。 这里对涉及的知识点不会讲的那么细,但提供完整的源码供参考,如对你有帮助,还请关注和star,你的关注是我分享的动力
先来简单介绍下页面逻辑,大体分为两部分
- 周刊列表页:描述周刊有多少期
- 周刊详情页:右边有分类和过往期刊
开发要求,希望通过这一章学习到的东西
- restful api的设计规范。 为什么要学习restful,作为前端api接口使用方,了解一套软件架构风格是有必要的。不管是自己全栈开发,还是使用别人的服务,都需要了解其中设计的合理性,没有理论支撑,你就会觉得所有的设计都好像还行,反正都可以满足需求,这样自己无法提高,别人也无法提高。
这里也简单说下restful的规范和我们的项目的分析过程
RestFul HTTP请求规范
- GET(SELECT):查询:从服务器取出资源
- POST(CREATE):新增:在服务器上新建一个资源
- PUT(UPDATE):更新:在服务器上更新资源(客服端提供改变后端后的完整资源)
- PATCH(UPDATE):更新:在服务器上更新部分资源(客服端提供改变的属性)
- DELETE(DELETE): 删除: 从服务器上删除资源 RestFul HTTP uri设计规范
- 用名字不用动作 比如我们设计周刊GET请求 文章 /article 表示所有文章 期刊 /week 表示所有期刊 分类/category 表示所有分类 尽量和DB名保持一致
- 用子路由表示具体的标识的数据 比如GET请求 文章 /article/1 表示文章ID为1的文章详情
- 用GET参数 ?表示条件筛选 比如GET请求 文章 /article?week=1&category=前端 表示条件筛选week=1和前端分类下面的所有文章
- nestjs和swagger搭配 提供的详细API可视化。
- nestjs和typeorm搭配,如何连接数据库
- nestjs如何统一封装拦截返回
- nestjs entity和dto的区别和使用
- dev/sit/uat不同环境是如何在项目中配置
需求背景和要求基本清晰,下面就是一步步来全栈来实现这个前端周刊, 文末有源码,也可以直接clone下来跑。每次关键节点也会有commit记录,方便一步一步调试
理清需求和要求,正式进入开发
先从最简单最独立的Category模块开始,建立整个开发框架
通过nestjs脚手架 配置开发环境
1 npm i -g @nestjs/cli
2 nest new weekly-backend/ 生成初始化项目 可以启动基本上就成功一半了
3 npm run start
4 http://localhost:3000/
能看到helloworld就成功一大半了
创建module/controller/service/entity/dto 等文件
这里nest提供快捷创建文件的命令,可以通过nest -h 查看nest提供的快捷命令
- 比如 nest g mo category 创建category的module
- 比如 nest g co category 创建category controller
- 比如 nest g s category 创建的category service
配置Swagger,让提供服务的API可视化,这里使用nextjs/swagger 可以很方便的配置Swagger
在main.ts配置
配置DB mysql 采用typeorm的方式可以很方便的连接数据库
app.module.js
这里我们分环境将DB配置抽出来。通过cross-env DEPLOY_ENV=dev 设置环境变量,适配多套环境。当然生产环境最好是接入配置中心,避免明文显示密码。
编写controller和service 这里category只提供一个接口,获取所有的分类,通过事先初始化的方式初始化所有分类。提供接口的URL为GET /category 这也是符合restful的规范的,GET请求获取数据,使用名字非动词
controller
import { Controller, Get } from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { CategoryService } from './category.service';
import { CategoryDto } from './dto/category.dto';
@Controller('category')
@ApiTags('分类')
export class CategoryController {
constructor(private readonly categoryService:CategoryService){}
@Get()
@ApiOperation({summary:'显示分类列表'})
index():Promise<CategoryDto []>{
return this.categoryService.findAll()
}
}
service
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CategoryEntity } from './category.entity';
import { CategoryDto } from './dto/category.dto';
@Injectable()
export class CategoryService {
constructor(
@InjectRepository(CategoryEntity)
private readonly categoryRepository:Repository<CategoryEntity>
){}
async findAll(): Promise<CategoryDto[]> {
return await this.categoryRepository.find();
}
}
entity
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class CategoryEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 240 })
name: string;
}
module
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CategoryController } from './category.controller';
import { CategoryEntity } from './category.entity';
import { CategoryService } from './category.service';
@Module({
imports:[TypeOrmModule.forFeature([CategoryEntity])],
controllers:[CategoryController],
providers:[CategoryService]
})
export class CategoryModule {}
dto
import { ApiProperty } from "@nestjs/swagger"
export class CategoryDto{
@ApiProperty({description:'分类名称',example:"前端"})
name:string
}
配置nextjs统一拦截返回,
main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { ResponseInterceptor } from './interceptors/response.interceptor'
import { HttpExceptionFilter } from './interceptors/all-exception.filter'
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const config = new DocumentBuilder()
.setTitle('Z技术周刊')
.setDescription('技术周刊 API description')
.setVersion('1.0')
.addTag('技术周刊')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api-docs', app, document);
app.useGlobalPipes(new ValidationPipe())
app.useGlobalFilters(new HttpExceptionFilter()); // http异常拦截
app.useGlobalInterceptors(new ResponseInterceptor()); // http返回的拦截
await app.listen(3000);
}
bootstrap();
all-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';
import { execPath } from 'process';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const status = exception.getStatus();
const res = exception.getResponse()
response
.status(status)
.json({
code: res['errcode'],
message:res['errmsg']
});
}
}
response.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable()
export class ResponseInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next
.handle()
.pipe(
map(data => ({
code: 200,
data,
message: 'success'
}))
)
}
}
第一阶段开发总结
上述的代码都在github.com/denditang/w… 如对你帮助,还请star,你的关注是我分享的动力,上述的介绍大体都是使用层面,涉及到的知识点还需要深入去了解。
- restful api的设计规范
- swagger api的可视化
- typeorm 连接数据库
- 统一封装nestjs的返回格式
- nest dto/entity的使用和区别