node.js上除了Express还有哪些好用的web开发框架

时间:2023-02-07 16:42:51
老司机都有体会, 开发本身没有多难, 最纠结其实是最初的技术和框架选型, 本没有绝对的好坏之分, 可一旦选择了不适合于自己业务场景的框架, 将来木已成舟后开发和维护成本都很高, 等发现不合适的时候更换的成本更是令人胆颤, 数据观最早的接入层是采用ThinkPHP开发, 后来基于种种权衡后决定用node.js重制, web开发框架选型就成为首要必须慎重解决的问题, Express当然是头号映入视野的名字, 本着全面考察重点择优的原则又花不少时间简单研究对比几个主流的node.js web开发框架, 真的有乱花渐欲迷人眼的感觉, 把当初列入备选的几个框架简单评论一下:
  • Express 
    这个就无需再介绍了吧, 几乎已经成为闭眼推荐的首位, 有点在于简单灵活, 缺陷也在于过于简单, 相当于每个功能都需要自己选择不同组件搭建, 虽然有各自脚手架帮助, 但对于开发大一点的系统还是缺乏必要的代码框架, 光搭建整合基础框架就会花不少时间, 对于新上手node.js不久的笔者而言,*灵活也意味着容易犯错, 最好有类似Djongo/ThinkPHP那样out-of-box即开即用的开发框架快速上手而不是坐而论道.
优点: 插件众多, 简单, *, 丰俭由人, 适合于简单业务逻辑模型 
缺点: 缺少规范性, 需要自己选择搭配的组件太多, 不太适合应用复杂的业务.
  • Koa 
    Express基于ES6的升级版, async/await解决ES5 callback hell的痼疾, 但是选择框架不仅仅是框架本身, 同时还要看插件扩展的丰富和成熟度, 因为没有用过担心后面遇坑填不平而放弃, 做研究可以大胆, 做产品必须谨慎.
优点: ES6语法, 逻辑易懂 
缺点: 刚开始应用不久, 担心有扩展不足和不兼容问题
  • Meteor 
    一个完全统一前后台开发的一站式框架, 从后台数据库到前端view全部包含在内, 特别适合于重度依赖websocket的SPA(单页面应用)开发, 国外流行的Asana就是完全采用Meteor框架开发.
优点:一站式解决方案, 前后台一体开发, 强大的websocket + mongoDB支持 
缺点:*度不够, 和传统的Web框架概念差异较大.
  • socketstream 
    如果说RESTful是Ajax的概念基础, socketstream实际上更接近于早期RPC的思路, 将函数调用(function name + parameters)建构在websocket协议层之上, 例如下面这段代码其实就是调用远程函数计算.

//-- server side in /server/rpc/app.jsexports.actions = function(req, res, ss){// return list of actions which can be called publiclyreturn {square: function(number){res(number * number);}}} //-- client requestss.rpc('app.square', 25)

优点: 完全web socket, 函数+参数==>返回值概念简单 
缺点:和主流的RESTful概念偏离较大, 很少看到实际应用案例
  • Sails.js 
    这是本文的主角, 相当于针对典型应用框架所需组件在Express基础上的集成封装, 把日常开发常用的功能都给你集成好了, 开箱即用, 完全兼容Express的middleware, 如果了解ThinkPHP就更容易上手了, RoR / Convention over Configuration的概念可以立即进入实际业务开发, 反正做什么事情应该怎么做人家都给你规定好了, 可以从实验代码逐步迭代到中大项目的生产代码.
除了传统的HTTP RESTful外还同时支持websocket - 同一个请求协议既可以通过Ajax发送, 也可以通过websocket发送, 这一点让人赞赏.优点: 开箱即用的全功能Express增强框架, 内置支持websocket 
缺点:(据说)ORM性能不好
 node.js上除了Express还有哪些好用的web开发框架
经过半个月的技术调研和评估后最终决定将原来基于ThinkPHP开发的接入控制层全部移植到Sails.js框架上, 虽然难免遇到技术问题需要解决, 但整体而言进展顺利, API访问延迟从100ms降低到10几ms,主要是从此有了一个Service Runtime解决PHP无状态而强依赖Redis的问题.
Sails.js 框架组成介绍既然是一个全功能的MVC框架, 那么开发一个应用所需的常用组件自然包含在内除非你有特殊要求, 这一点非常类似ThinkPHP, 从目录分布上介绍一个概貌先:
  • api/ 
    Model / Controller都在这个目录下, 所有代码根据不同职责都被定义好位置, 这一点非常有利于大型项目开发的代码规划.
  • api/controllers 
    这里面写的每一个controller + function 都会被自动映射到路由上, 充分体现convention over configuration的特点, 与ThinkPHP如出一辙, 来一个最基础的hello world吧 - api/controllers/TestController.hello() 方法被自动映射到 /test/hello 而无需任何配置.

// api/controllers/TestController.jsmodule.exports = {hello: function (req, res) {return res.ok("Hello World");},

controller下可以随意进行目录分层, 每增加一级目录就相当增加一级路由.api/controllers/user/TestController.hello() ==> /user/test/hello 
 
  • api/services 
    项目大了程序就必须进行职能分层, 业务相关都作为独立的service一层放在这个目录下, controller仅负责request / response及差错处理, 既简化controller的代码逻辑, 又可以让service代码被不同模块复用。作用相当于一套全局函数库。
常见错误: service代码不要直接通过res做应答, 那本来是属于controller的职责.
  • api/policies 
    作为前后台分离必然要提供Ajax API给前端, 那么访问鉴权和安全控制必不可少, Sails.js在Express middleware基础上封装为policy实现访问控制, 如果说上面那个 /test/hello 仅允许登录用户才能访问, 那么加一个验证session的policy就是.
langset是检测多语言设置的policy, sessionAuth仅允许带有登录session的API请求通过, 多个policy可以组成一个检查链, 只有全通过了才能达到最终的controller, 中间任何一个policy都可以直接response拒绝服务.config/policies的鉴权配置

module.exports.policies = {'*': true,TestController: {'*' : 'langset','hello' : ['langset', 'sessionAuth'],},

api/policies/sessionAuth.js实现如果是页面请求则redirect, 如果是ajax则应答403 forbidden. 
这里还借用了passport的req.isAuthenticated(), 根据业务需要可以结合多种检查方式, 把每个独立检查点都作为一个policy, 然后配置为一个policy chain, 功能独立又可以灵活搭配, 类似应用登录检查中的异地登录, 长时间未登录, 连续密码错误, 动态验证码校验都可以用policy的思路去实现.

module.exports = function (req, res, next) {// User is allowed, proceed to the next policy, // or if this is the last policy, the controllerif (req.isAuthenticated()) {return next();} // User is not allowed// (default res.forbidden() behavior can be overridden in `config/403.js`)if (req.wantsJSON) {return res.forbidden('You are not permitted to perform this action.');}return res.redirect("/login");};
 
 
  • api/responses 
    常用的response页面都在这里了, 当然可以增加新的, 调用就是res.{response_name}(message)

* badRequest.js - 400* created.js - 201* forbidden.js - 403* notFound.js - 404* ok.js - 200* serverError.js - 500

Oops, 404了。node.js上除了Express还有哪些好用的web开发框架
 
  • api/models 
    Sails.js的ORM实现, 这个目录下的class都会映射为一个持久化的object及attributes, 采用Waterline hook机制实现, 可以配置为MySQL/MongoDB/Oracle/PostgreSQL多种常见数据库.
考虑到node.js更多作为中间转接层角色出现,真正ORM与核心业务逻辑还是交由Java等后台实现为妥,所以个人并不建议过多采用Sails.js自身的ORM,聊胜于无吧。
  • assets/ 
    images/fonts/js/css/模板等静态文件下载根路径,build时可采用Grunt进行打包丑化等操作。
  • config/
    • session.js - 配置session可采用的存储模式,redis/mongo等多种存储方式可简单配置支持。
    • http.js - default按照缺省配置就够用了,如果还想自己微调可以完全配置Express的middleware次序。笔者在middleware里面增加了一个客制化的 response-time 中间件用于记录每个API请求的log。
    • log.js - 配置log输出appender,CAUTION:输出log一定要用sails.log而不是console.log,因为可以根据需要配置log输出方式而不修改代码,笔者采用了sails-hook-winston+winston-daily-rotate-file实现按日期命名log文件。
    • routes.js/policies.js - 路由和安全策略配置双剑合璧,简单而强大。
    • views.js - 配置View模板库。
  • views/ 
    如果是正式项目个人更建议用handlebars替换缺省的ejs模板库,优雅替代模板里面的js代码逻辑,尤其可以自定义helper函数瞬间让开发者不必再依赖模板库提供的有限逻辑处理,充分发挥函数式语言的优势。
这是原生ejs模板实现的403:

<h2><% if (typeof data !== 'undefined') { %><%= data %><% } else { %>You don't have permission to see the page you're trying to reach.<% } %></h2>

这是handlebars实现的403

<div class="content">{{#if data }}{{data}}{{else}}您没有权限查看您要访问的网页。{{/if}}</div>
Sails.js框架常用搭配的七种武器框架自身所能提供的功能毕竟是有限的,扩展库才是一个框架丰满与否的关键,此处结合笔者的实践推荐几个可用的框架扩展。1. 长生剑-async.js + lodash.js 
这是两个被内置的核心js库,前者用于流程控制,后者用于数据结构处理,即使不用sails.js也应该深入掌握。
2. 碧玉刀-handlebars 
放弃ejs吧,handlebars更优雅简洁,而且具有更强更容易的处理逻辑扩展能力。全部秘密就在于注册一个自己的函数。
这是一个实现编码转义的helper函数

Handlebars.registerHelper('unescape', function (v) {return unescape(v);});

3. 离别钩-winston 
写后台不充分考虑log记录简直等于不带手机去旅行,sails-hook-winston可以无缝将sails.log转接到任意winston transport插件上而无需修改任何代码。
console / winston-daily-rotate-file / http / LogstashRedis 是实际使用中的transport。4. 霸王枪-passport 
基于passport的第三方认证已经成为JS的标准,几百个知名strategy即插即用,几行配置代码就可以实现微信/微博/钉钉/QQ第三方登录认证,当然最好对OAuth2有简单了解。
5. 多情环-socket.io 
Sails.js缺省是自带websocket支持的,而且其model的CRUD操作也自动支持pub/sub操作,一个Ajax请求无论是走HTTP协议还是websocket协议在controller是同一个处理入口,header/session都是一样的,这一特点真切让人感到体贴,不必再针对不同功能采用不同协议然后生成不同代码了。
CAUTION: 不能用于阿里云防火墙后面的应用服务 
实测下来socket会频繁断掉重连,而且每次都是不同的IP地址,怀疑两点起因:
  • 阿里防火墙专门针对HTTP无状态短连接设计,不允许出现长期的socket连接。
  • 为了负载分担和恶意攻击防护,不同请求入口IP地址会变化。
6. 孔雀翎-pm2 
如果说Java的代码稳定是靠语法和Exception捕获来保证的,那么node.js的逻辑反其道行之-如果错了就尽快退出执行,反正catch回调函数抛出的异常就是水中月,进程生命周期管理最佳工具就是pm2,一行代码都不用写即可支持cluster运行模式,即使偶尔出错导致进程退出也会自动重启,确保服务不间断。
7. 拳头-typescript 
后台应用服务最关键的要求是什么?稳定,稳定,还是TMD的稳定。 
面对一个过于*没有类型检查的javascript语言,防范编码错误的最佳武器就是套上类型语言的盔甲,class / interface / extends / enum etc,少量代码时这是累赘,面对大量代码和复杂结构与逻辑时有语言的支撑才是保命的秘诀。
后记写了这么多,总结了自己在开发数据观中间层时候的一些经验希望对各位Web开发者有些许参考,其实更可悲的是现今Web开发并没有能一统*的框架(而且短期还真看不到这个希望),随着时间流逝和技术的飞速发展,恁多风流难免被雨打风吹去,枉回首,如落红湮灭。