关于nest:Nest-快速通关攻略

写在结尾每一篇文章都是作者用 心 写出,还须要破费大量工夫去校对调整,旨在给您带来最好的浏览体验。 您的 点赞、珍藏、转发 是对作者的最大激励,也能够让更多人看到本篇文章,万分感激! 如果感觉本文对您有帮忙,还请帮忙在 github 上点亮 star 激励一下吧! 注释Nest 是一个用于构建高效,可扩大的 Node.js 服务器端应用程序的框架。它应用渐进式 JavaScript,内置并齐全反对 TypeScript 并联合了 OOP(面向对象编程),FP(函数式编程)和 FRP(函数式响应编程)的元素。 在底层,Nest 应用弱小的 HTTP Server 框架,如 Express(默认)和 Fastify。Nest 在这些框架之上提供了肯定水平的形象,同时也将其 API 间接裸露给开发人员。这样能够轻松应用每个平台的有数第三方模块。 从上图也能够看出,Nest 目前是热度仅次于老牌 Express,目前排名第二的 Nodejs 框架。 明天,咱们通过本篇 Nest 疾速通关攻略,应用 Nest 来打造一个游览攻略,将应用到包含但不限于 Nest 的下列性能 中间件管道类验证器守卫拦截器自定义装璜器数据库文件上传MVC权限...本我的项目有一个指标,针对 Nest 文档中的简略案例,放到理论场景中,从而找到最佳实际。 好了,话不多说,咱们筹备开始吧! 初始化我的项目本案例的源码仓库在 源码地址 可下载。 首先,应用 npm i -g @nestjs/cli 命令装置 nest-cli,而后应用脚手架命令创立一个新的 nest 我的项目即可。(如下) nest new webapi-travel我的项目初始化实现后,咱们进入我的项目,运行 npm run start:dev 命令启动我的项目吧!(我的项目启动后关上 (http://localhost:3000/)[http://localhost:3000/] 可查看成果) ...

April 5, 2022 · 9 min · jiezi

在vscode中调试nest

网上方法如果用了tsconfig.paths功能就报错 { "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "Debug Nest Framework", "args": ["${workspaceFolder}/src/main.ts"], "runtimeArgs": ["--nolazy", "-r", "ts-node/register"], "sourceMaps": true, "cwd": "${workspaceRoot}", "protocol": "inspector", "console": "integratedTerminal" } ]}根据nodemon-debug.json改良后{ "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "Debug Nest Framework", "args": ["${workspaceFolder}/src/main.ts"], "runtimeArgs": ["--nolazy", "-r", "ts-node/register", "-r", "tsconfig-paths/register"], "sourceMaps": true, "cwd": "${workspaceRoot}", "protocol": "inspector", "console": "integratedTerminal" } ]}

May 4, 2019 · 1 min · jiezi

NestJS 集成graphql grpc 分布式实践

前言: 为了学习nestjs graphql grpc 微服务方面的知识,具体grpc和graphql的语法再之后在做详细分析1 创建项目npm i -g @nestjs/clinest new project-name2 添加graphql创建graphql-config.service.ts文件,用于graphql的配置及编写过滤器的逻辑@Injectable()export class GraphQLConfigService implements GqlOptionsFactory { constructor() {} createGqlOptions(): GqlModuleOptions { return { typePaths: [join(process.cwd(), “./graphqls/*.graphql”)], // 配置的graphql文件地址 installSubscriptionHandlers: true, definitions: { path: join(process.cwd(), “src/graphql.schema.ts”), // 解析之后的文件地址 outputAs: “class” }, context: async ({ req }) => { // 过滤器 let user = Jwt.verifyToken(req.headers.authorization); // 业务逻辑 return { user }; } }; }}添加进app.module.ts里@Module({ imports: [ GraphQLModule.forRootAsync({ imports: [ApplicationModule], useClass: GraphQLConfigService }), ],})export class ApplicationModule {}创建文件xxx.resolvers.ts @Query(‘getUser’) async getUser() { return {}; }3 添加grpc首先,创建一个子项目xxx子项目创建grpc.options.ts文件,用于init连接的配置export const grpcClientOptions: ClientOptions = { transport: Transport.GRPC, options: { url: “localhost:50051”, // 服务地址 package: “xxx”, protoPath: join(__dirname, ‘./xxx.proto’), },};创建接口, 注意这里的首字母会被自动装为大写 @GrpcMethod(“UserService”) async addUser(data: User): Promise<any> { return data }在main.ts引入import { grpcClientOptions } from ‘./grpc.options’;async function bootstrap() { const app = await NestFactory.create(AppModule); app.connectMicroservice(grpcClientOptions); await app.startAllMicroservicesAsync();}bootstrap();其他微服务或是apigateway调用是创建一个 const grpcClientOptions: ClientOptions = { transport: Transport.GRPC, options: { url: grpcServe.user.url, package: grpcServe.user.package, protoPath: join(__dirname, ‘../../common/proto/user.proto’), },};@Injectable()export class ClentServe { constructor() {} @Client(grpcClientOptions) public readonly client: ClientGrpc;} ...

March 13, 2019 · 1 min · jiezi

一个开源vue网站博客,nuxt开源网站,前后端分离项目

unNue.com开媛笔记,基于nuxt ssr首屏服务器端渲染 。用于分享、记录、交流和学习,希望可以帮助到小伙伴们。同时网站在不断更新,创造属于猿(媛)的世界 -$Bao Yalong ..Let’s Go! https://unnue.com简述前端 Github地址语言:Javascript主框架:Nuxt状态管理:Vuex路由:vue-router网络请求:axios编辑器:markdown-it代码高亮:highlight.js日期处理:moment头像:gravatar第三方登录:QQ服务端 ~~ 即将开源语言:Typescript主框架:Nestjs数据库:Mysql数据库ORM:typeorm静态资源:七牛云CDN:七牛云后台管理 ~~ 暂未开源语言:Javascript前端框架:Vue后台管理UI:Element服务器系统:CentOS运行环境:Nodejs反向代理:Nginx进程管理:Pm2项目管理:Git关于服务器的部署查阅这篇文章 CentOS从零开始部署Nodejs项目预览前台后台管理最终效果: https://unnue.com

December 13, 2018 · 1 min · jiezi

nest后端开发实战(二)——分层

前言分层是解决软件复杂度很好的方法,它能够降低耦合、增加复用。典型的java后端开发大多分为三层,几乎成了标准模式,但是node社区对于分层的讨论却很少。node后端是否需要分层?如何分层?本文将从个人的角度提供一些思路。是否必要分层?如何分层?个人的结论是:如果想做一个正儿八经的node后台应用,一定需要分层,java的三层架构,同样适用于node。结构如下:dao层dao(data access object),数据访问对象,位于最下层,和数据库打交道。它的基本职责是封装数据的访问细节,为上层提供友好的数据存取接口。一般是各种数据库查询语句,缓存也可以在这层做。无论是nest还是egg,官方demo里都没有明确提到dao层,直接在service层操作数据库了。这对于简单的业务逻辑没问题,如果业务逻辑变得复杂,service层的维护将会变得非常困难。业务一开始一般都很简单,它一定会向着复杂的方向演化,如果从长远考虑,一开始就应该保留dao层。分享两点dao层的建议:1、以实体为中心定义类型描述。后端建模的一大产出是领域实体模型,后续的业务逻辑其实就是对实体模型的增删改查。利用ts对类型的丰富支持,可以先将实体模型的类型描述定义出来,这将极大的方便上层业务逻辑的实现。我一般会将实体相关的类型、常量等都定义到一个文件,命名为xxx.types.ts。定义到一个文件的好处是,编码规范好落实,书写和引用也非常方便,由于没有太多逻辑,即使文件稍微大一点,可读性也不会降低太多。用po和dto来描述实体及其周边。po是持久化对象和数据库的表结构一一对应;dto数据传输对象则很灵活,可以在丰富的场景描述入参或返回值。下面是个user实体的例子:// user.types.ts/** * 用户持久化对象 /export interface UserPo { id: number; name: string; // 姓名 gender: Gender; // 性别 desc: string; // 介绍}/* * 新建用户传输对象 /export interface UserAddDto { name: string; gender?: Gender; desc?: string;}/* * 性别 /export enum Gender { Unknown, Male, Female,}虽然ts提供了强大的类型系统,如果不能总结出一套最佳实践出来,同样会越写越乱。全盘使用不是一个好的选择,因为这样会失去很多的灵活性。我们需要的是在某些必须的场景,坚持使用。2、不推荐orm框架orm的初心很好,它试图完全将对象和数据库映射自动化,让使用者不再关心数据库。过度的封装一定会带来另外一个问题——隐藏复杂度的上升。个人觉得,比起查询语句,隐藏复杂度更可怕。有很多漂亮的orm框架,比如java界曾经非常流行的hibernate,功能非常强大,社区也很火,但实际在生产中使用的人却很少,反倒是一些简单、轻量的被大规模应用了。而且互联网应用,对性能的要求较高,因此对sql的控制也需要更直接和精细。很多互联网公司也不推荐使用外键,因为db往往是瓶颈,关系的维护可以在应用服务器做,所以orm框架对应关系的定义不一定能用得上。node社区有typeorm,sequelizejs等优秀的orm框架,个人其实并不喜欢用。我觉得比较好的是egg mysql插件所使用的ali-rds。它虽然简单,却能满足我大部分的需求。所以我们需要的是一个好用的mysql client,而不是orm。我也造了一个类似的轮子bsql,我希望api的设计更加接近sql的语意。目前第一个版本还比较简单,核心接口已经实现,还在迭代,欢迎关注。下面是user.dao的示例。import { Injectable } from ‘@nestjs/common’;import { BsqlClient } from ‘bsql’;import { UserPo, UserAddDto } from ‘./user.types’;@Injectable()export class UserDao { constructor( private readonly db: BsqlClient, ) { } /* * 添加用户 * @param userAddDto / async addUser(userAddDto: UserAddDto): Promise<number> { const result = await this.db.insertInto(‘user’).values([userAddDto]); return result.insertId; } /* * 查询用户列表 * @param limit * @param offset / async listUsers(limit: number, offset: number): Promise<UserPo[]> { return this.db.select<UserPo>(’’).from(‘user’).limit(limit).offset(offset); } /** * 查询单个用户 * @param id / async getUserById(id: number): Promise<UserPo> { const [user] = await this.db.select<UserPo>(’’).from(‘user’).where({ id }).limit(1); return user; }}从广义的角度看,dao层很像公式“程序=数据结构+算法”中的数据结构。“数据结构”的实现直接关系到上层的“算法”(业务逻辑)。service层service位于dao之上,使用dao提供的接口,也可以调用其它service。service层也比较简单,主要是弄清其职责和边界。1、实现业务逻辑。service负责业务逻辑这点毋庸置疑,核心是如何将业务逻辑抽象成接口及其粒度。service层应该尽量提供功能相对单一的基础方法,更多的场景和变化可以在controller层实现。这样设计有利于service层的复用和稳定。2、处理异常。service应该合理的捕获异常并将其转化成业务异常,因为service层是业务逻辑层,他的调用方更关心业务逻辑进行到哪一步了,而不是一些系统异常。在实现上,可以定义一个business.exception.ts,里面包含常见的业务异常。当遇到业务逻辑执行不下去的问题时,抛出即可,调用方既能根据异常的类型采取行动。// common/business.exception.ts/** * 业务异常 /export class BusinessException { constructor( private readonly code: number, private readonly message: string, private readonly detail?: string, ) { }}/* * 参数异常 /export class ParamException extends BusinessException { constructor(message: string = ‘参数错误’, detail?: string) { super(400, message, detail); }}/* * 权限异常 /export class AuthException extends BusinessException { constructor(message: string = ‘无权访问’, detail?: string) { super(403, message, detail); }}对于业务异常,还需要一个兜底的地方全局捕获,因为不是每个调用方都会捕获并处理异常,兜底之后就可以记录日志(方便排查问题)同时给与一些友好的返回。在nest中统一捕获异常是定义一个全局filter,代码如下:// common/business-exception.filter.tsimport { ExceptionFilter, Catch, ArgumentsHost } from ‘@nestjs/common’;import { BusinessException } from ‘./business.exception’;/* * 业务异常统一处理 */@Catch(BusinessException)export class BusinessExceptionFilter implements ExceptionFilter { catch(exception: BusinessException, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); response.json({ code: exception.code, message: exception.message }); console.error(// tslint:disable-line ‘BusinessException code:%s message:%s \n%s’, exception.code, exception.message, exception.detail); }}// main.tsimport { NestFactory } from ‘@nestjs/core’;import { AppModule } from ‘./app.module’;import { BusinessExceptionFilter } from ‘./common/business-exception.filter’;async function bootstrap() { const app = await NestFactory.create(AppModule); // 注册为全局filter app.useGlobalFilters(new BusinessExceptionFilter()); await app.listen(3000);}bootstrap();3、参数校验。dao层设计很简单,几乎不做参数校验,同时dao也一般不会开放给外部直接调用,而是开放service。所以service层应该做好参数校验,起到保护的作用。4、事务控制。dao层可以针对单个的持久化做事物控制,粒度比较小,而基于业务原则的事物处理就应该在service层。nest目前貌似没有在service层提供事务的支持。接下来我准备做个装饰器,在service层提供数据库本地事物的支持。分布式事务比较复杂,有专门的方法,后面有机会再介绍。controller层controller位于最上层,和外部系统打交道。把这层叫做“业务场景层”可能更贴切一点,它的职责是通过service提供的服务,实现某个特定的业务场景,并以http、rpc等方式暴露给外部调用。1、聚合参数前端传参方式有多种:query、body、param。有时搞不清楚到底应该从哪区,很不方便。我一般是自定义一个@Param()装饰器,把这几种参数对象聚合到一个。实现和使用方式如下:// common/param.tsimport { createParamDecorator } from ‘@nestjs/common’;export const Param = createParamDecorator((data, req) => { const param = { …req.query, …req.body, …req.param }; return data ? param[data] : param;});// user/user.controller.tsimport { All, Controller } from ‘@nestjs/common’;import { UserService } from ‘./user.service’;import { UserAddDto } from ‘./user.types’;import { Param } from ‘../common/param’;@Controller(‘api/user’)export class UserController { constructor(private readonly userService: UserService) { } @All(‘add’) async addUser(@Param() user: UserAddDto) { return this.userService.addUser(user); } @All(’list’) async listUsers( @Param(‘pageNo’) pageNo: number = 1, @Param(‘pageSize’) pageSize: number = 20) { return this.userService.listUsers(pageNo, pageSize); }}2、统一返回结构一个api调用,往往都有个固定的结构,比如有状态码和数据。可以将controller的返回包装一层,省去一部分样板代码。下面是用Interceptor的一种实现:// common/result.tsimport { Injectable, NestInterceptor, ExecutionContext } from ‘@nestjs/common’;import { Observable } from ‘rxjs’;import { map } from ‘rxjs/operators’;export interface Response<T> { data: T; code: number; message: string;}@Injectable()export class ResultInterceptor<T> implements NestInterceptor<T, Response<T>> { intercept( context: ExecutionContext, call$: Observable<T>, ): Observable<Response<T>> { return call$.pipe(map(data => ({ code: 200, data, message: ‘success’ }))); }}所有的返回将会包裹在如下的结构中:3、参数校验还是留给service层吧nest提供了一套针对请求参数的校验机制,功能很强大。但使用起来会稍微繁琐一点,实际上也不会有太多复杂的参数校验。个人觉得参数校验可以统一留给service,assert库可能就把这个事情搞定了。小结本文讲的都是一些很小的点,大多是既有的理论。这些东西不想清楚,写代码时就会非常难受。大家可以把这里当做一个规范建议,希望能提供一些参考价值。上一篇:nestjs后端开发实战(一)——依赖注入 ...

November 13, 2018 · 2 min · jiezi

nestjs后端开发实战(一)——依赖注入

前言js单线程和无阻塞io让它在处理高并发时有着得天独厚的优势,node应运而生,从此js进入到后端开发的行列。但是目前js在后端开发领域,并没有得到广泛和深度的应用。原因可能有这几点:异步代码非常难看,回调地域。没有类型系统,ide不友好,不利于大规模应用的开发和维护。缺乏相对标准的开发范式和开发框架。其中第一点,目前async await已经非常成熟,不成构成问题;对于第二点,如果引入ts也将不是问题。ts完全兼容js,有类型系统又不失灵活,设计优雅适合大规模程序开发;至于第三点,有很多框架正在试图解决该问题,比如egg、sails以及本文要讨论的nest。nest是一个对标spring的后端开发框架,目前还年轻,但发展速度挺快。egg和sails没有深度使用,只是有所关注,没有太多发言权。他们解决的问题差不多,只是感觉实现方式有点“硬”,不够自然。或许是因为个人早前有java的经历,所以更适应nest这套。java几乎是后端开发的标准,nest把那套实践了多年的理念借鉴过来,或许能够有些奇妙的化学反应。再结合js的灵活性以和性能优势,说不定也是轻量级后端开发的一个好选择。接下来,笔者准备写一系列的文章来介绍nest后端开发的实践,欢迎关注。后端开发和依赖注入我先尝试着把依赖注入解释清楚,这是nest的核心,所以从这里开始。前端开发和后端开发其实很不一样。前端开发比较零碎,ui、交互、部分逻辑,而后端主要专注于逻辑。所以后端开发非常需要一种编程范式,以支持复杂的领域模型和业务逻辑管理。目前实践得比较成熟的是面向对象的思想,而对于前端开发,面向对象的诉求其实并不大。有了面向对象这个前提后,对象的依赖、创建、生命周期管理等就成了一个问题,依赖注入(DI)正是提供了一种标准方式来解决此问题。它将依赖的创建和销毁交给“容器”去管理,使用者只管用,不操心具体细节。这也是控制反转(IOC)思想的一种实现。上面这段话说得比较抽象,现实一点,个人觉得它比较方便的解决了两类问题:上下文相关的依赖注入。就是需要根具不同的上下文注入不同的实例,共享上下文的状态。异步依赖的注入。下面通过两个例子来解释。例一,解释上下文相关依赖问题。先看代码:class OrderDao { …}class OrderService { private orderDao: OrderDao; constructor() { // 依赖OrderDao this.orderDao = new OrderDao(); } …}OrderService依赖OrderDao,并且在构造函数中实例化了依赖对象。这是一种强依赖关系,如果想在不同的上下文改变orderDao的实例就比较麻烦了。实际编程中可能存在类似场景,比如,跑测试用例的时候,想把dao换成mock的实现。要达到上面的目的,代码得先重构一下:interface IOrderDao { …}class OrderDaoImpl implements IOrderDao { …}class OrderDaoMockImpl implements IOrderDao { …}class OrderService { private orderDao: IOrderDao; // 依赖接口而不是实例 constructor(orderDao: IOrderDao) { this.orderDao = orderDao; } …}上面的代码只是一种设计模式,和依赖注入无关。这种模式的思想是面向接口编程,而不是具体实现,从而达到解耦的目的。如果有依赖注入的容器,那么只需简单配置,容器会帮你管理依赖的创建和生命周期。具体的配置后面会讲到。例二,解释异步依赖问题。假设OrderDao依赖mongo访问数据库,但是mongo client的创建却是异步的。同时我们还希望mongo client是单例,因为不希望频繁的创建数据库连接。下面是无依赖注入情况下的一种可能实现:// 连接数据库的示例代码const MongoClient = require(‘mongodb’).MongoClient;const url = ‘mongodb://localhost:27017’;const dbName = ‘myproject’;MongoClient.connect(url, function(err, client) { // 在这里才能拿到client操作数据库 const db = client.db(dbName); // …});class OrderDao { private mongo; constructor() { //异步的方式拿到mongo client }}能解决问题,只是代码会难看一点。由于是异步,还可能存在使用OrderDao的时候,mongo并没有连接好,此时调用会出错。如果有依赖注入,就能比较优雅的处理此类问题。以上说到的两类场景,实际编程遇到的可能并不多,可能10%都不到,但是一旦遇上又非常难受。使用依赖注入,能够优雅的解决上面的问题,同时代码也更加规范。但使用依赖注入也是有一点点成本的,需要写一点点的样板代码。依赖注入还具备传染性,就是某个对象使用了依赖注入,依赖它的对象也必须使用,否则就乱套了。个人的看法是,首先还是保持简洁,对象尽量设计成上下文无关或无状态,只是在核心层(controller service, dao)使用依赖注入。在nest中使用依赖注入前面写了这么多,现在看下怎么在nest中写依赖注入。样板代码很简单,大致是这样:1、依赖方通过@Injectable()修饰,告诉容器,“我是需要注入的”,同时在构造函数中声明依赖。实例化时,依赖对象将通过构造函数注入。// order.service.ts@Injectable()export class OrderService { // 注意这里是个简写,等价于在OrderService下面定义了orderDao字段,同时在构造函数中给与赋值 constructor(private readony orderDao: OrderDao) {}}2、定义providor,服务提供者。nest中有三种providor:class、value、factory。class providor就是普通的class,会被实例化后注入给依赖方;value providor可以是任意类型的值,直接注入给依赖方;factory providor是一个工厂方法,容器将先执行该方法,然后将返回值注入给依赖方,factory支持支持异步方法。3、配置依赖关系。nest中有module的概念,主要用于描述在该scope下,具体的依赖和输出关系。下面的代码展示了三种providor的配置。import {OrderDao} from ‘./order.dao’;// class providorconst classProvidor = { // 这也是class providor,和????效果一样 provide: OrderDao, useClass: OrderDao}const valueProvidor = { // value providor provide: ‘Config’, useValue: process.env.NODE_ENV === ‘prod’ ? {…} : {…}}const factoryProvidor = { // factory provoidr provide: ‘Mongo’, useFactory: async () => { const client await MongoClient.connect(…); return client.db(dbName); }}@Module({ providers: [OrderDao, valueProvidor, factoryProvidor] // 塞到这里})export class OrderModule {}4、依赖关系的解析。除了全局module(通过@Global()修饰即可成为全局module),其它module都是一个单独的scope。容器在创建对象时,会在当前scope和全局scope查找依赖。在决定具体使用哪个依赖时,会通过类型匹配或者具名的方式查找。两种使用方式都很简单,代码如下:class OrderService { constructor( readonly orderDao: OrderDao, // class匹配,通过在scope内搜索同类型class的providor @Inject(‘Config’) config,// 具名匹配,通过在scope内搜索该名字的provoid ) {}}剩下的就交给容器帮你创建和管理对象了。结语开篇写得比较简单,主要是关于为什么要依赖注入的思考。接下来可能会逐步分享实践方面的一些东西,比如项目结构,分层,基础设施等具体问题的解决方案,欢迎关注。 ...

October 9, 2018 · 1 min · jiezi