[译]Node.js 框架比较: Express vs. Koa vs. Hapi

1 介绍

Express.js无疑是当前Node.js中最流行的Web应用程序框架。它几乎成为了大多数Node.js web应用程序的基本的依赖,甚至一些例如Sails.js这样的流行的框架也是基于Express.js。然而你还有一些其余框架的选择,能够给你带来“sinatra”同样的感受(译注:sinatra是一个简单的Ruby的Web框架,能够参考这篇博文)。另外两个最流行的框架分别是Koa和Hapi。javascript

这篇文章不是打算说服你哪一个框架比另一个更好,而是只是打算让你更好地理解每一个框架能作什么,什么状况下一个框架能够秒杀另一个。java

2 框架的背景

咱们将要探讨的两个框架看起来都很是类似。每个都可以用几行代码来构建一个服务器,并均可以很是轻易地构建REST API。咱们先瞧瞧这几个框架是怎么诞生的。node

2.1 Express

 

2.1 Express

2009年6月26日,TJ Holowaychuk提交了Express的第一次commit,接下来在2010年1月2日,有660次commits的Express 0.0.1版本正式发布。TJ和Ciaron Jessup是当时最主要的两个代码贡献者。在第一个版本发布的时候,根据github上的readme.md,这个框架被描述成:git

疯通常快速(而简洁)的服务端JavaScript Web开发框架,基于Node.js和V8 JavaScript引擎。github

差很少5年的时间过去了,Express拥有了4,925次commit,如今Express的最新版本是4.10.1,由StrongLoop维护,由于TJ如今已经跑去玩Go了。web

2.2 Koa

大概在差很少一年前的2013年8月17日,TJ Holowaychuk(又是他!)只身一人提交了Koa的第一次commit。他描述Koa为“表现力强劲的Node.js中间件,经过co使用generators使得编写web应用程序和REST API更加丝般顺滑”。Koa被标榜为只占用约400行源码空间的框架。Koa的目前最新版本为0.13.0,拥有583次commits。

express

2.3 Hapi

2011年8月5日,WalmartLabs的一位成员Eran Hammer提交了Hapi的第一次commit。Hapi本来是Postmile的一部分,而且最开始是基于Express构建的。后来它发展成本身本身的框架,正如Eran在他的博客里面所说的:segmentfault

Hapi基于这么一个想法:配置优于编码,业务逻辑必须和传输层进行分离..api

Hapi最新版本为7.2.0,拥有3,816次commits,而且仍然由Eran Hammer维护。服务器

 

全部开发者要开发Node.js web应用程序的第一步就是构建一个基本的服务器。因此咱们来看看用这几个框架构建一个服务器的时候有什么异同。

 

3 建立一个服务器

全部开发者要开发Node.js web应用程序的第一步就是构建一个基本的服务器。因此咱们来看看用这几个框架构建一个服务器的时候有什么异同。

3.1 Express

var express = require('express');
var app = express(); 

var server = app.listen(3000, function() { 
    console.log('Express is listening to http://localhost:3000'); 
});

对于全部的node开发者来讲,这看起来至关的天然。咱们把express require进来,而后初始化一个实例而且赋值给一个为app的变量。接下来这个实例初始化一个server监听特定的端口,3000端口。app.listen()函数实际上包装了node原生的http.createServer()函数。

3.2 Koa

var koa = require('koa');
var app = koa();

var server = app.listen(3000, function() {
    console.log('Koa is listening to http://localhost:3000');
});

 

你立刻发现Koa和Express是很类似的。其实差异只是你把require那部分换成koa而不是express而已。app.listen()也是和Express如出一辙的对原生代码的封装函数。

3.3 Hapi

var Hapi = require('hapi');
var server = new Hapi.Server(3000);

server.start(function() {
    console.log('Hapi is listening to http://localhost:3000');
});

Hapi是三者中最独特的一个。和其余二者同样,hapi被require进来了可是没有初始化一个hapi app而是构建了一个server而且指定了端口。在Express和Koa中咱们获得的是一个回调函数而在hapi中咱们获得的是一个新的server对象。一旦咱们调用了server.start()咱们就开启了端口为3000的服务器,而且返回一个回调函数。这个server.start()函数和Koa、Express不同,它并非一个http.CreateServer()的包装函数,它的逻辑是由本身构建的。



4 路由控制

如今一块儿来搞搞一下服务器最重要的特定之一,路由控制。咱们先用每一个框架分别构建一个老掉渣的“Hello world”应用程序,而后咱们再探索一下一些更有用的东东,REST API。

4.1 Hello world

4.1.1 Express

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 /”请求而后调用一个回调函数,这个回调函数会被传入reqres两个对象。这个例子当中咱们只利用了resres.send()来返回整个页面的字符串。Express有不少内置的方法能够用来进行路由控制。getpostputheaddelete等等这些方法都是Express支持的最经常使用的方法(这只是一部分而已,并非所有)。

 

4.1.2 Koa

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。全部带有*前缀的函数都表示这个函数会返回一个generator对象。根本上来讲,generator会同步地yield出数据(译注:若是对Python比较熟悉的话,应该对ES6的generator不陌生,这里的yield其实和Python的yield语句差很少一个意思),这个超出本文所探索的内容,不详述。在app.use()函数中,generator函数设置响应体。在Koa中,this这个上下文其实就是对node的requestresponse对象的封装。this.body是KoaResponse对象的一个属性。this.body能够设置为字符串, buffer, stream, 对象, 或者null也行。上面的例子中咱们使用了Koa为数很少的中间件的其中一个。这个中间件捕获了全部的路由而且响应同一个字符串。

 

4.1.3 Hapi

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方法能够处理典型的例如GETPUTPOSTDELETE的请求,*通配符能够匹配全部的路由。handler函数被传入一个request对象的引用,它必须调用reply函数包含须要返回的数据。数据能够是字符串、buffer、可序列化对象、或者stream。

 

4.2 REST API

Hello world除了给咱们展现了如何让一个应用程序运行起来之外几乎啥都没干。在全部的重数据的应用程序当中,REST API几乎是一个必须的设计,而且能让咱们更好地理解这些框架是能够如何使用的。如今让咱们看看这些框架是怎么处理REST API的。

4.2.1 Express

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');
});

4.2.1 Express

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.Router()和不能用app.user('/api', router)之外,其实上是和Express 3.x本质上是同样的。在Express 3.x中,你须要用app.route()替换router.route()而且须要加上/api前缀。Express 4.x的这种语法能够减小开发者编码错误而且你只须要修改少许代码就能够修改HTTP方法规则。

 

4.2.2 Koa

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这样的能够减小编码重复路由规则的能力。它须要额外的中间件来处理路由控制。我选择使用koa-route由于它是由Koa团队维护的,可是还有不少由其余开发者维护的可用的中间件。Koa的路由和Express同样使用相似的关键词来定义它们的方法,.get().put(),.post(), 和 .delete()。Koa在处理路由的时候有一个好处就是,它使用ES6的generators函数来减小对回调函数的处理。

 

4.2.3 Hapi

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路由处理的第一印象就是,相对于其它两个框架,这货是多么的清爽,可读性是多么的棒!即便是那些必须的methodpathhandlerreply配置参数都是那么的赏心悦目(译注:做者高潮了)。相似于Koa,Hapi不少重复的代码会致使更大的出错多可能性。然而这是Hapi的有意之为,Hapi更关注配置而且但愿使得代码更加清晰和让团队开发使用起来更加简便。Hapi但愿能够不须要开发者进行编码的状况下对错误处理进行优化。若是你尝试去访问一个没有被定义的REST API,它会返回一个包含状态码和错误的描述的JSON对象。

 

5 优缺点比较

5.1 Express

5.1.1 优势

Express拥有的社区不只仅是上面三者当中最大的,而且是全部Node.js web应用程序框架当中最大的。在通过其背后差很少5年的发展和在StrongLoop的掌管下,它是三者当中最成熟的框架。它为服务器启动和运行提供了简单的方式,而且经过内置的路由提升了代码的复用性。

5.1.2 缺点

使用Express须要手动处理不少单调乏味的任务。它没有内置的错误处理。当你须要解决某个特定的问题的时候,你会容易迷失在众多能够添加的中间件中,在Express中,你有太多方式去解决同一个问题。Express自夸为高度可配置,这有好处也有坏处,对于准备使用Express的刚入门的开发者来讲,这不是一件好的事情。而且对比起其余框架来讲,Express体积更大。

5.2 Koa

5.2.1 优势

Koa有着傲人的身材(体积小),它表现力更强;对比起其余框架,它使得中间件的编写变的更加容易。Koa基本上就是一个只有骨架的框架,你能够选择(或者本身写一个)中间件,而不用妥协于Express或者Hapi它们自带的中间件。它也是惟一一个采用ES6的框架,例如它使用了ES6的generators。

5.2.2 缺点

Koa不稳定,仍处于活跃的开发完善阶段。使用ES6仍是有点太超前了,例如只有0.11.9+的Node.js版本才能运行Koa,而如今最新的Node.js稳定版本是0.10.33。和Express同样有好也有坏的一点就是,在多种中间件的选择仍是本身写中间件。就像咱们以前所用的router那样,有太多相似的router中间件可供咱们选择。

5.3 Hapi

5.3.1 优势

Hapi自豪地宣称它本身是基于配置优于编码的概念,而且不少开发者认为这是一件好事。在团队项目开发中,能够很容易地加强一致性和可复用性。做为有着大名鼎鼎的WalmartLabs支持的框架和其余响当当的企业在实际生产中使用Hapi,它已经通过了实际战场的洗礼,企业们能够没有担心地基于Hopi运行本身的应用程序。全部的迹象都代表Hapi向着成为的伟大的框架的方向持续成熟。

5.3.2 缺点

Hapi绝逼适合用来开发更大更复杂的应用。但对于一个简单的web app来讲,它的可能有点儿堆砌太多样板代码了。并且Hapi的可供参考样例太少了,或者说开源的使用Hapi的应用程序太少了。因此选择它对开发者的要求更高一点,而不是所使用的中间件。

 

6 总结

咱们已经看过三个框架一些棒棒的并且很实际的例子了。Express毫无疑问是三个当中最流行和最出名的框架。当你要开发一个新的应用程序的时候,使用Express来构建一个服务器可能已经成为了你的条件反射了;但但愿如今你在作选择的时候会多一些思考,能够考虑选择Koa或者Hapi。Koa经过超前拥抱ES6和Web component的思想,显示了Web开发社区正在进步的对将来的承诺。对于比较大的团队和比较大的项目来讲,Hapi应该成为首要选择。它所推崇的配置优于编码,对团队和对团队一直追求的可复用性都大有裨益。如今赶忙行动起来尝试使用一个新的框架,可能你会喜欢或者讨厌它,但没到最后你总不会知道结果是怎么样的,有一点无容置疑的是,它会让你成为一个更好的开发者。

 

 

英文原文:Node.js Framework Comparison: Express vs. Koa vs. Hapi

做者:Jonathan

译者:戴嘉华

译文:戴嘉华博客segmentfault

相关文章
相关标签/搜索