本文翻译自: node
https://www.airpair.com/node.js/posts/nodejs-framework-comparison-express-koa-hapigit
直至今日,Express.js仍然是最为流行的Node.js Web应用程序框架。它彷佛已经逐渐成为大多数Node.js Web应用程序的基础依赖框架,包括不少流行的框架,好比Sail.js就是以Express.js为基础搭建的。然而如今咱们有了更多“类sinatra”(注:sinatra是一款Ruby框架,代码很是简洁,号称开发一个博客项目只须要100行代码)似的框架能够选择。也就是接下来咱们将分别介绍的Koa和Hapi两个框架。es6
本文的目的并非打算去说服你们去使用其中的任何一款框架,而是但愿可以帮助你们去对比分析这三个框架的优劣势。github
今天咱们对比的这三款框架其实都有不少的共通点。好比他们均可以几行代码就能建立一个服务,并且进行REST API的开发也是小菜一碟。下面咱们就分别来看这三款框架吧。web
2009年6月26日,TJ Holowaychuk 第一次提交了Express的代码。在2010年1月2日,Express正式发布了0.0.1版本,截止当时,做者已经提交了超过660次代码。当时Express的两位主要开发维护者分别是TJ 以及 Ciaron Jessup。初版发布的时候,Express在Github的readme.md介绍文件中式这么描述这块框架的:express
一款基于node.js以及Chrome V8引擎,快速、极简的JS服务端开发框架。 api
5年多后今天,Express目前已经发布到4.10.1版本,提交超过4925次代码,目前主要是采用StrongLoop进行开发维护,由于TJ同窗已经转入GO语言开发社区了。promise
Koa是在一年之前也就是在2013年8月17日由TJ同窗(是的,仍是他...)首次提交的代码。他当时是这么描述Koa的:“更具备表现力,更健壮的Node.js中间件。基于co组件的generators处理异步回调,不管是Web应用仍是REST API开发,你的代码都将变得更加优雅”。(注:Koa2发布后,已经放弃了引入co组件,而是开始采用ES7的async/await语法处理异步回调)。轻量化的Koa号称不超过400行代码。(注:SLOC是源代码行数,又分为物理代码行数LOC,以及逻辑代码行数LLOC)。截止目前,Koa已经发布了0.13.0版本,超过585次的代码提交。app
Hapi是由来自于沃尔玛实验室的Eran Hammer同窗在2011年8月5日首次提交的。本来他只是Postmile(这是一款在node.js上开发的协做列表工具,服务端由Hapi完成)的一个核心部件,一样也是基于Express开发。后来Hapi才被独立出来做为一款框架进行开发维护,Eran同窗在他的博客里这样说道:框架
“Hapi的核心思想是配置优于代码,因此业务代码必须从传输层中剥离出来”
至今为止,Hapi已经提交超过3816次代码,版本是7.2.0,当前仍然是由Eran Hammer进行主要开发维护。
OK,最后让咱们来经过社区的统计数据来看看这三个框架的活跃程度:
参考项 |
Express.js |
Koa.js |
Hapi.js |
Github点赞数 |
16158 |
5846 |
3283 |
代码贡献者 |
163 |
49 |
95 |
依赖包数量 |
3828 |
99 |
102 |
StackOverFlow提问数 |
11419 |
72 |
82 |
基本上每一个刚开始接触Node.js的开发者第一步操做就是建立一个服务。由于下面咱们将依次使用每一个框架来分别建立一个服务,来看看他们之间的类似处与不一样的地方。
var express = require('express'); var app = express(); var server = app.listen(3000, function() { console.log('Express is listening to http://localhost:3000'); });
上面的操做对于大多数Node开发者来讲应该都是很熟练了。咱们先引入express,而后建立一个实例对象并将其赋值给变量app。接下来是实例化一个服务,而且开始监听3000端口。app.listen() 其实就是对nodejs原生的http.createServer()进行了一层封装。
var koa = require('koa'); var app = koa(); var server = app.listen(3000, function() { console.log('Koa is listening to http://localhost:3000'); });
显而易见,Koa的语法和Express很是类似。其实来讲你只须要将引入express修改成引入koa便可。一样的,app.listen() 也是对http.createServer()进行了一层封装。
var Hapi = require('hapi'); var server = new Hapi.Server(3000); server.start(function() { console.log('Hapi is listening to http://localhost:3000'); });
Hapi的语法比较特别一些。不过,第一步仍是引入hapi,可是这里是实例化存入一个hapi app变量中,而后就能够建立一个指定端口的服务了。在Express和Koa中这一步咱们获得的是一个回调函数,可是Hapi返回的是一个server对象。一旦咱们经过server.start()来调用这个在3000端口的服务之后,他将会返回一个回调函数。而后跟Koa和Express不同的地方在于,这个回调并非对http.CreateServer()进行的一层封装,而是Hapi本身实现的逻辑。
接下来咱们继续深刻了解做为一个服务的一个重要功能,那就是路由。第一步咱们将使用每一个框架来分别建立一个“Hello World”应用,而后再继续关注一些更实用的功能,REST API。
var express = require('express'); var app = express(); app.get('/', function(req, res) { res.send('Hello world'); }); var server = app.listen(3000, function() { console.log('Express is listening to http://localhost:3000'); });
咱们使用get()方法来捕获“GET /”请求,而后调用一个回调函数来处理请求,该回调函数拥有两个参数:req与res。在这个例子中咱们仅仅使用了res的res.send()方法来向页面返回一个字符串。Express包含了不少内置的方法来处理路由功能。下面是几个Express中经常使用的方法(只是部分,并非所有方法):get, post, put, head, delete…
var koa = require('koa'); var app = koa(); app.use(function *() { this.body = 'Hello world'; }); var server = app.listen(3000, function() { console.log('Koa is listening to http://localhost:3000'); });
Koa和Express有些许的不一样之处,由于他使用了ES6 的generators语法。(注:generators是ES6提出的一种异步回调的解决方法,在ES7中将直接升级为async/await)在方法前面加上一个 * 表示该方法返回一个generator对象。generators函数的做用就是使得异步函数产生一些同步的值,可是这些值仍然是在当前的请求范围之类。(注:generator对经过yield 定义不一样的状态值,return也算是一个状态值。详情了解:http://es6.ruanyifeng.com/#docs/generator )在app.use()中,generator函数对响应体进行赋值。在Koa中this对象,其实就是对node的request与response对象进行的封装。this.body在Koa中是一个响应体对象的方法。它基本上能被赋值为任何值,字符串、buffer、数据流、对象或者是null。Koa核心库提供了不少中间件,这里咱们只是使用了其中的一个,这个中间件能够捕获全部的路由,而后响应一个字符串。
var Hapi = require('hapi'); var server = new Hapi.Server(3000); server.route({ method: 'GET', path: '/', handler: function(request, reply) { reply('Hello world'); } }); server.start(function() { console.log('Hapi is listening to http://localhost:3000'); });
这里咱们使用了由server对象提供的一个内置方法:server.route(),这个方法须要这些参数:path(必填)、method(必填)、vhost以及handler(必填)。这个HTTP方法能够处理咱们常见的GET/PUT/POST/DELETE请求,也可使用*来处理全部路由请求。回调函数会被Hapi默认传入request对象以及reply方法,reply是必须被执行的方法,并且须要传入一项数据,这个数据能够是字符串、序列化的对象或者流。
Hello World程序历来都没有太多的指望,由于它只能展现建立及运行一个应用最基本最简单的操做。REST API几乎是全部大型应用程序所必须的一个功能,同时对于咱们更好的理解这些框架有很大的帮助。所以接下来咱们将看看这几个框架是如何来处理REST API。
var express = require('express'); var app = express(); var router = express.Router(); // REST API router.route('/items') .get(function(req, res, next) { res.send('Get'); }) .post(function(req, res, next) { res.send('Post'); }); router.route('/items/:id') .get(function(req, res, next) { res.send('Get id: ' + req.params.id); }) .put(function(req, res, next) { res.send('Put id: ' + req.params.id); }) .delete(function(req, res, next) { res.send('Delete id: ' + req.params.id); }); app.use('/api', router); // index app.get('/', function(req, res) { res.send('Hello world'); }); var server = app.listen(3000, function() { console.log('Express is listening to http://localhost:3000'); });
咱们在现有的Hello World程序上增长了REST API。Express提供了一些缩写的方法来处理路由。这是Express 4.x 版本的语法,其实跟Express 3.x 版本差很少,一样但愿你再也不使用express.Router()方法,而是换成新的API:app.use('/api', router)。新的API可让咱们使用app.route()来替换以前的router.route(),固然了须要添加一个描述性的动词/api.这是一个不错的修改,由于下降开发者出现错误的机会,同时对原有的HTTP方法进行了最小的一个修改。
var koa = require('koa'); var route = require('koa-route'); var app = koa(); // REST API app.use(route.get('/api/items', function*() { this.body = 'Get'; })); app.use(route.get('/api/items/:id', function*(id) { this.body = 'Get id: ' + id; })); app.use(route.post('/api/items', function*() { this.body = 'Post'; })); app.use(route.put('/api/items/:id', function*(id) { this.body = 'Put id: ' + id; })); app.use(route.delete('/api/items/:id', function*(id) { this.body = 'Delete id: ' + id; })); // all other routes app.use(function *() { this.body = 'Hello world'; }); var server = app.listen(3000, function() { console.log('Koa is listening to http://localhost:3000'); });
很明显,Koa并不能像Express那样去下降route动词的重复性。它同时还须要引入一个独立的中间件来处理路由。我选择使用koa-route,是由于他主要是由Koa小组来开发维护,固然也还有不少其余开发者贡献的路由中间件能够选择。从方法名的关键字上来看,koa的路由和express也是很是类似的,例如.get(), .put(), .post(), 以及 .delete()。
Koa在处理路由有一个优点,它使用了ES6 的generator函数,从而下降了回调函数的复杂度。
var Hapi = require('hapi'); var server = new Hapi.Server(3000); server.route([ { method: 'GET', path: '/api/items', handler: function(request, reply) { reply('Get item id'); } }, { method: 'GET', path: '/api/items/{id}', handler: function(request, reply) { reply('Get item id: ' + request.params.id); } }, { method: 'POST', path: '/api/items', handler: function(request, reply) { reply('Post item'); } }, { method: 'PUT', path: '/api/items/{id}', handler: function(request, reply) { reply('Put item id: ' + request.params.id); } }, { method: 'DELETE', path: '/api/items/{id}', handler: function(request, reply) { reply('Delete item id: ' + request.params.id); } }, { method: 'GET', path: '/', handler: function(request, reply) { reply('Hello world'); } } ]); server.start(function() { console.log('Hapi is listening to http://localhost:3000'); });
跟其余框架相比,Hapi的路由配置给人的第一印象就是代码清爽,可读性高。甚至连必填的配置参数method,path,hanlder以及reply都很是容易辨别。跟Koa同样,Hapi路由的代码重复性也比较高,因此出错的概率也比较大。之全部这么作,是由于Hapi更但愿使用配置来完成路由,这样咱们的代码会更清爽,在小组内也会更容易的维护。Hapi一样试图去提升代码错误处理能力,由于有的时候他甚至不须要开发者编写任何代码(注:意思是彻底都过配置实现,回调函数也是用默认的。这样出错的 几率就小了不少,也更容易上手)。若是你试图去访问一个没有在REST API中定义的路由,那么Hapi将会返回一个包含状态值与错误信息的JSON对象。
Express拥有最大社区,比仅仅是跟这三个框架相比,而是对于全部的Nodejs框架来讲也是最大的。目前来讲,他是最为三者中最为成熟的框架,接近5年的开发投入,同时还采用了StrongLoop(注:StrongLoop是一个进程管理工具,提供CLI与UI界面。)对线上仓库的代码进行管理。他提供了一种简单的方式来建立和运行一个服务,同时路由的内置也使得代码获得了重复使用。
在使用Express过程当中,咱们每每要处理不少单调乏味的任务。好比他没有内置的错误处理机制,另外对于一样一个问题能够有不少中间件来供选择,这也使得开发者容易迷失在中间件的选择中,总而言之就是,一个问题你会有N多解决方案。Express声称本身是可配置选择的,这其实不没有好或很差,可是对于一个刚刚接触Express的开发者来讲,这就是他的劣势了。另外,Express跟其余的框架相比也还有很大的差距。
Koa的一个小进步就是他的代码比较富有表现力,开发中间件也比其余框架更容易得多。Koa是一个很基础的准系统框架,开发者能够选择(或开发)他们所须要的中间件,而不是去选择Express或Hapi的中间件。他同时也是三者中惟一一个积极拥抱ES6的框架,好比采用了ES6 generators函数。
目前Koa还处于不稳定版本,还处在开发阶段。使用ES6进行开发的确是处于领先水平,好比Koa须要基于Nodejs 0.11.9以上的版本运行,而目前nodejs的文本版本是0.10.33。这是一件能够算做好也能够算做很差的事情,就像Express开发者有不少中间件要选择甚至本身开发中间件同样。好比咱们在上面看到的同样,对于路由来讲就有不少中间件供咱们选择。
Hapi一直很自豪的说他们的框架是配置优于代码,固然也有不少开发者可能会质疑把这一点算做是优点。但这一点对于大型项目组来讲,的确是能够保持代码的统一性以及代码复用性。另外这款框架是由沃尔玛实验室支持的,也有不少大公司在线上环境使用Hapi,代表他已经经过了严峻的测试,由于这些公司会考虑得更多才会使用Hapi来运行他们的项目。所以全部的这些迹象都代表Hapi正在朝一个伟大的框架发展。
Hapi的定位更倾向于大型或复杂的应用程序。对于一个简单的应用来讲,Hapi在代码上反而有些显得冗余了,另外目前Hapi所提供的样例程序也比较少,使用Hapi进行开发的开源应用一样不多。所以,若是选择Hapi的话,你可能要投入更多精力进行开发,而不是简单的调用一个第三方中间件。
咱们已经看了三个框架还算不错具备表明性的一些样例代码。Express仍然是当下最为流行,以及最被人所知晓的框架。当开始一个新的开发项目时,可能你们的第一反应就是用Express来建立一个服务。可是如今更但愿你们多考虑考虑使用Koa或者Hapi。Koa积极拥抱ES6的语法,展现了promise的真正魅力。目前整个web开发社区也都意识到ES6的优点,正在逐步往上面迁移。Hapi应该是大型项目组或者大型项目的第一选择。他所倡导的配置优于代码会使得项目组 在代码的重复性上受益匪浅,这也正是大多数项目组所追求的目标。如今行动起来,尝试一款新的框架吧,可能你会喜欢他也可能会讨厌他,但若是不去尝试你永远也不会知道结果是什么,最终全部的这些经历都会让你成长为一个更加优秀的开发者。