NodeJS框架对比 - Express、Koa、Egg、Nest等
NodeJS框架对比 - Express、Koa、Egg、Nest等
Last edited 2022-5-12

1.Express.js

Express.js 是 Node.JS 诞生之初,最早出现的一款框架,现在仍然很流行,作者是TJ。
Express是基于回调,也是node中最常见的Error-First 的模式(第一个参数是error对象)
一个简单的Express服务器
const express = require('express'); const app = express(); /* 中间件 */ app.use((req, res, next) => { console.log('middleware'); next(); console.log('middleware call'); }); /* 路由部分 */ const router = express.Router(); router.get('/', (req, res) => { res.send('Home'); }); app.use(router); /* 静态文件 */ app.use(express.static('./')); app.listen(3000);
随着ECMAScript的发展,推出了generator yield 语法,JS向同步方式写异步代码迈出了一步,作为回应,TJ大神推出了Koa.js。

2.Koa.js

Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。 Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。
不同于Express,Koa是使用的号称异步终极解决方案的Async/Await,也就是基于Promise,使用Try-Catch来捕获错误。koa与express 提供的API大致相同,express是大而全,内置了大多数的中间件,更让人省心,koa2不绑定任何的框架,干净简洁,小而精,更容易实现定制化,扩展性好。
Koa.js是一款微型Web框架,写一个hello world很简单,但web应用离不开session,视图模板,路由,文件上传,日志管理。这些 Koa 都不提供,需要自行去官方的 Middleware 寻找。然而,100个人可能找出100种搭配。
一个简单的Koa服务器
const Koa = require('koa'); const Router = require('koa-router'); const serve = require('koa-static'); const app = new Koa(); const router = Router(); /* 中间件 */ app.use(async (ctx, next) => { console.log('middleware'); next(); console.log('middleware call'); }); /* 路由部分 */ router.get('/', (ctx) => { ctx.body = 'Home'; }); app.use(router.routes()); /* 静态文件 */ app.use(serve('./')); app.listen(3000);

Koa 对比 Koa2

koa2与koa1的最大区别是koa2实现异步是通过async/await,使用await next()进入下一个中间件;koa1实现异步是通过使用generator函数,yield next进入下一个中间件,而express实现异步是通过回调函数的方式。

3.EGG.js

Egg.js是基于Koa.js,解决了上述问题,将社区最佳实践整合进了Koa.js,另取名叫Egg.js,并且将多进程启动,开发时的热更新等问题一并解决了。这对开发者很友好,开箱即用,开箱即是最(较)佳配置。
Egg.js 是一个约定大于配置的框架
Egg 奉行『约定优于配置』,按照一套统一的约定进行应用开发,团队内部采用这种方式可以减少开发人员的学习成本,开发人员不再是『钉子』,可以流动起来。
正因为如此,Egg.js 中对于目录的规范是有一个约束的,一个基础的 Egg.js 项目的目录结构如下:
egg-project ├── package.json ├── app.js (可选) ├── agent.js (可选) ├── app | ├── router.js │ ├── controller │ | └── home.js │ ├── service (可选) │ | └── user.js │ ├── middleware (可选) │ | └── response_time.js │ ├── schedule (可选) │ | └── my_task.js │ ├── public (可选) │ | └── reset.css │ ├── view (可选) │ | └── home.tpl │ └── extend (可选) │ ├── helper.js (可选) │ ├── request.js (可选) │ ├── response.js (可选) │ ├── context.js (可选) │ ├── application.js (可选) │ └── agent.js (可选) ├── config | ├── plugin.js | ├── config.default.js │ ├── config.prod.js | ├── config.test.js (可选) | ├── config.local.js (可选) | └── config.unittest.js (可选) └── test ├── middleware | └── response_time.test.js └── controller └── home.test.js

egg缺点:

  1. 单实例多进程模式,架构异常复杂,为了提升一点点性能,所增加的复杂度完全不可接受。如今serverless的时代,为了性能容器化部署,多实例才是主流。用egg,如果单机性能不满足的情况下,还是要上多实例,那么egg应用就需要同时处理单实例下的多进程和集群下的多实例,到最后你会发现前者除了复杂度暴涨外一点用都没有,高性能多实例就可以,何必白白增加复杂度。要性能就多实例,不追求性能的小服务单实例单进程完全满足你的需要。
  1. 开发体验,egg调试体验有多差,他们自己应该很清楚。打个断点有多难,控制台报错信息是给人看的吗?调试全靠ctrl+z/y,加console打印。
  1. 插件机制,万物挂ctx,还不带类型提示(靠别人自觉写d.ts),接别人的项目一脸懵逼。插件机制异常黑盒,只能靠配置文件。
  1. 约定大于配置,处处是约定,想整理目录都没办法。代码组织不应该按照类型分,我看到那种一股脑儿把service放一个地方的项目真的想死,代码组织最佳实践应该以模块为单位。
  1. 生态维护力度很低,完善都也很低,很多插件使用方式很不优雅,不成体系,几乎是在hack。
  1. Egg.js没有原生提供的TypeScript支持, 开发时可使用egg-ts-helper 来帮助自动生成 d.ts 文件
可以尝试一下nestjs吧,了解一下ioc,你会发现很多以前组织不好的东西都可以用ioc搞定,这是一套开发逻辑异常统一的工具,理解它会打开新世界的大门。从此就对egg这类不成体系的web框架再无好感,甚至你还顺便理解了Angular的组织方式,去学习使用ng会有豁然开朗的感觉。

4.Nest.js

Nestjs 是一个将 Typescript 与 Nodejs Framework 结合的产物。
 
Nestjs 不是一个新轮子,它是基于 Express、http://socket.io 封装的 nodejs 后端开发框架,对 Typescript 开发者提供类型支持,也能优雅降级供 Js 使用,拥有诸多特性。Nest是基于Express实现的,需要的话可以取到底层的对象,如requestresponse
初看 NestJS ,对于 Vue 和 React 技术栈的人来说,NestJS 的思维方式确实不那么容易理解。但是假如你接触过 AngularJS,也许会有一些熟悉感。那要是你曾经是一个后端开发人员,熟练使用 Java 和 Spring 的话,可能就会跳起来大喊一声:这不就是个 Spring boot 吗!
你的直觉没错,NestJS 和 AngularJS,Spring 类似,都是基于控制反转(IoC = Inversion of Control)原则来设计的框架,并且都使用了依赖注入(DI = Dependency Injection)的方式来解决耦合的问题。

Modules, Controllers, Components

Nestjs 开发围绕着这三个单词,Modules 是最大粒度的拆分,表示应用或者模块。Controllers 是传统意义的控制器,一个 Module 拥有多个 Controller。Components 一般用于做 Services,比如将数据库 CRUD 封装在 Services 中,每个 Service 就是一个 Component。

装饰器路由

装饰器路由是个好东西,路由直接标志在函数头上,做到了路由去中心化:
@Controller() export class UsersController { @Get('users') getAllUsers() {} @Get('users/:id') getUser() {} @Post('users') addUser() {} }
装饰器的用法,几乎和java的Spring注解一模一样。以前用过 Go 语言框架 Beego,就是采用了中心化路由管理方式,虽然引入了 namespace 概念,但当协作者多、模块体量巨大时,路由管理成本直线上升。Nestjs 类似 namespace 的概念通过装饰器实现:
@Controller('users') export class UsersController { @Get() getAllUsers(req: Request, res: Response, next: NextFunction) {} }
访问 /users 时会进入 getAllUsers 函数。可以看到其 namespace 也是去中心化的。

模块间依赖注入

Modules, Controllers, Components 之间通过依赖注入相互关联,它们通过同名的 @Module @Controller @Component 装饰器申明,如:
@Controller() export class UsersController { @Get('users') getAllUsers() {} } @Component() export class UsersService { getAllUsers() { return [] } } @Module({ controllers: [ UsersController ], components: [ UsersService ], }) export class ApplicationModule {}
在 ApplicationModule 申明其内部 Controllers 与 Components 后,就可以在 Controllers 中注入 Components 了:
@Controller() export class UsersController { constructor(private usersService: UsersService) {} @Get('users') getAllUsers() { return this.usersService.getAllUsers() } }

装饰器参数

与大部分框架从 this.req 或 this.context 等取请求参数不同,Nestjs 通过装饰器获取请求参数:
@Get('/:id') public async getUser( @Response() res, @Param('id') id, ) { const user = await this.usersService.getUser(id); res.status(HttpStatus.OK).json(user); }
@Response 获取 res,@Param 获取路由参数,@Query 获取 url query 参数,@Body 获取 Http body 参数。
 
 

6.Midway

Midway 是阿里巴巴 - 淘宝前端架构团队,基于渐进式理念研发的 Node.js 框架。
Midway 基于 TypeScript 开发,结合了面向对象(OOP + Class + IoC)函数式(FP + Function + Hooks)两种编程范式,并在此之上支持了 Web / 全栈 / 微服务 / RPC / Socket / Serverless 等多种场景,致力于为用户提供简单、易用、可靠的 Node.js 服务端研发体验

为什么要有 Midway

社区上也有很多类似的框架,那为什么还需要 Midway ?
原因有三点
  1. Midway 是阿里内部一直持续在研发的框架,之前 egg 是作为底层框架,需要有面向应用层面的框架来和集团场景对接
  1. 全量使用 TypeScript 是未来一段时间的趋势,面向未来去迭代和研发是作为架构组创新的要求
  1. 虽然社区已经有 nest 这样的框架,但是这些产品的维护、协作、修改都会受到商业化产品的制约,也无法做到需求的快速迭代和安全性保障,整体的研发理念也和我们不同,为此,我们需要有一套自研的框架体系

优势

  1. Midway 框架是在内部已经使用 5 年以上的 Node.js 框架,有着长期投入和持续维护的团队做后盾。
  1. 已经在每年的大促场景经过考验,稳定性无须担心
  1. 丰富的组件和扩展能力,例如数据库,缓存,定时任务,进程模型,部署以及 Web,Socket 甚至 Serverless 等新场景的支持
  1. 一体化调用方案可以方便快捷和前端页面协同开发
  1. 良好的 TypeScript 定义支持
  1. 国产化文档和沟通容易简单
 

面向对象(OOP + Class + IoC)

Midway 支持面向对象的编程范式,为应用提供更优雅的架构。
下面是基于面向对象,开发路由的示例。
// src/controller/home.ts import { Controller, Get } from '@midwayjs/decorator'; import { Context } from '@midwayjs/koa'; @Controller('/') export class HomeController { @Inject() ctx: Context @Get('/') async home() { return { message: 'Hello Midwayjs!', query: this.ctx.ip } } }

函数式(FP + Function + Hooks)

Midway 也支持函数式的编程范式,为应用提供更高的研发效率。
下面是基于函数式,开发路由接口的示例。
// src/api/index.ts import { useContext } from '@midwayjs/hooks' import { Context } from '@midwayjs/koa'; export default async function home () { const ctx = useContext<Context>() return { message: 'Hello Midwayjs!', query: ctx.ip } }
 
 

NodeJS后端与Java后端对比

NodeJS用于后端开发,对比传统的Java服务端开发有哪些异同。

优势与劣势

  • 优势:占用内存小,启动迅速,不用处理多线程问题,非阻塞高并发,少了Setter Getter和Java各种List/ArrayList/HashMap等...代码编写更简易。
  • 劣势:运行时类型的检查,严谨的类型判断,TypeScript对反射与Decorator功能的支持不足导致框架无法更完善的封装高级功能。

性能

动态语言原来无法和静态语言比性能。 但是nodejs出现 直接把动态语言的性能提升了好几个数量级。V8就是快, 结合跑多个nodejs进程 竟然性能非常好。PHP现在也追了上来,比以前PHP强的多,虽然比nodejs还是差点。
 

总结

NodeJS后端已经有很好生态,各类工程化与微服务需要功能都有了npm版本,内部数据通过TypeScript验证保证类型无误,只要处理好输入数据的验证和转换问题,是可以在实际项目中发挥力量的。
如果您必须选择一种服务器端技术(Node.js或Spring Boot)来为未来十年的业务提供动力,那会是什么?
人们会说这取决于。如果要构建依赖于大量I / O的应用程序(FinTech,预订系统,媒体应用程序等),则将使用Node.js;但是,如果需要执行大量的计算(物联网,电子商务平台,大数据),那么会使用Spring Boot。
如果赢家只有一个怎么选?Node.js。
  • 全栈开发
  • 写作课笔记 | 先完成完美,先写完再修改一个简单寻找高需求、低竞争产品的方法
    • Waline
    • Giscus
    • Cusdis