歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Node.js框架對比:Express/Koa/Hapi

Node.js框架對比:Express/Koa/Hapi

日期:2017/3/1 9:09:58   编辑:Linux編程

1、介紹

  直至今日,Express.js仍然是最為流行的Node.js Web應用程序框架。它似乎已經逐漸成為大多數Node.js Web應用程序的基礎依賴框架,包括很多流行的框架,比如Sail.js就是以Express.js為基礎搭建的。然而現在我們有了更多“類sinatra”(注:sinatra是一款Ruby框架,代碼非常簡潔,號稱開發一個博客項目只需要100行代碼)似的框架可以選擇。也就是接下來我們將分別介紹的Koa和Hapi兩個框架。

  本文的目的並不是打算去說服大家去使用其中的任何一款框架,而是希望能夠幫助大家去對比分析這三個框架的優劣勢。

2、框架背景

  今天我們對比的這三款框架其實都有很多的共通點。比如他們都可以幾行代碼就能創建一個服務,而且進行REST API的開發也是小菜一碟。下面我們就分別來看這三款框架吧。

2.1、Express

  2009年6月26日,TJ Holowaychuk 第一次提交了Express的代碼。在2010年1月2日,Express正式發布了0.0.1版本,截止當時,作者已經提交了超過660次代碼。當時Express的兩位主要開發維護者分別是TJ 以及 Ciaron Jessup。第一版發布的時候,Express在Github的readme.md介紹文件中式這麼描述這塊框架的:

一款基於node.js以及Chrome V8引擎,快速、極簡的JS服務端開發框架。

  5年多後今天,Express目前已經發布到4.10.1版本,提交超過4925次代碼,目前主要是采用StrongLoop進行開發維護,因為TJ同學已經轉入GO語言開發社區了。

2.2、Koa

  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次的代碼提交。

2.3、Hapi

  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

3、創建服務

  基本上每個剛開始接觸Node.js的開發者第一步操作就是創建一個服務。因為下面我們將依次使用每個框架來分別創建一個服務,來看看他們之間的相似處與不同的地方。

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,然後創建一個實例對象並將其賦值給變量app。接下來是實例化一個服務,並且開始監聽3000端口。app.listen() 其實就是對nodejs原生的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非常相似。其實來說你只需要將引入express修改為引入koa即可。同樣的,app.listen() 也是對http.createServer()進行了一層封裝。

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,但是這裡是實例化存入一個hapi app變量中,然後就可以創建一個指定端口的服務了。在Express和Koa中這一步我們得到的是一個回調函數,但是Hapi返回的是一個server對象。一旦我們通過server.start()來調用這個在3000端口的服務以後,他將會返回一個回調函數。然後跟Koa和Express不一樣的地方在於,這個回調並不是對http.CreateServer()進行的一層封裝,而是Hapi自己實現的邏輯。

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 /”請求,然後調用一個回調函數來處理請求,該回調函數擁有兩個參數:req與res。在這個例子中我們僅僅使用了res的res.send()方法來向頁面返回一個字符串。Express包含了很多內置的方法來處理路由功能。下面是幾個Express中常用的方法(只是部分,並不是全部方法):get, post, put, head, delete…

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語法。(注:generators是ES6提出的一種異步回調的解決方法,在ES7中將直接升級為async/await)在方法前面加上一個 * 表示該方法返回一個generator對象。generators函數的作用就是使得異步函數產生一些同步的值,但是這些值仍然是在當前的請求范圍之類。(注:generator對通過yield 定義不同的狀態值,return也算是一個狀態值。generator函數對響應體進行賦值。在Koa中this對象,其實就是對node的request與response對象進行的封裝。this.body在Koa中是一個響應體對象的方法。它基本上能被賦值為任何值,字符串、buffer、數據流、對象或者是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方法可以處理我們常見的GET/PUT/POST/DELETE請求,也可以使用*來處理所有路由請求。回調函數會被Hapi默認傳入request對象以及reply方法,reply是必須被執行的方法,而且需要傳入一項數據,這個數據可以是字符串、序列化的對象或者流。

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

  我們在現有的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方法進行了最小的一個修改。

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那樣去降低route動詞的重復性。它同時還需要引入一個獨立的中間件來處理路由。我選擇使用koa-route,是因為他主要是由Koa小組來開發維護,當然也還有很多其他開發者貢獻的路由中間件可以選擇。從方法名的關鍵字上來看,koa的路由和express也是非常相似的,例如.get(), .put(), .post(), 以及 .delete()。

  Koa在處理路由有一個優勢,它使用了ES6 的generator函數,從而降低了回調函數的復雜度。

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的路由配置給人的第一印象就是代碼清爽,可讀性高。甚至連必填的配置參數method,path,hanlder以及reply都非常容易辨別。跟Koa一樣,Hapi路由的代碼重復性也比較高,所以出錯的幾率也比較大。之所有這麼做,是因為Hapi更希望使用配置來完成路由,這樣我們的代碼會更清爽,在小組內也會更容易的維護。Hapi同樣試圖去提高代碼錯誤處理能力,因為有的時候他甚至不需要開發者編寫任何代碼(注:意思是完全都過配置實現,回調函數也是用默認的。這樣出錯的 概率就了很多,也更容易上手)。如果你試圖去訪問一個沒有在REST API中定義的路由,那麼Hapi將會返回一個包含狀態值與錯誤信息的JSON對象。

5、優劣勢

5.1 Express

5.1.1 優勢

  Express擁有最大社區,比僅僅是跟這三個框架相比,而是對於所有的Nodejs框架來說也是最大的。目前來說,他是最為三者中最為成熟的框架,接近5年的開發投入,同時還采用了StrongLoop(注:StrongLoop是一個進程管理工具,提供CLI與UI界面。)對線上倉庫的代碼進行管理。他提供了一種簡單的方式來創建和運行一個服務,同時路由的內置也使得代碼得到了重復使用。

5.1.2 劣勢

  在使用Express過程中,我們往往要處理很多單調乏味的任務。比如他沒有內置的錯誤處理機制,另外對於同樣一個問題可以有很多中間件來供選擇,這也使得開發者容易迷失在中間件的選擇中,總而言之就是,一個問題你會有N多解決方案。Express聲稱自己是可配置選擇的,這其實不沒有好或不好,但是對於一個剛剛接觸Express的開發者來說,這就是他的劣勢了。另外,Express跟其他的框架相比也還有很大的差距。

5.2 Koa

5.2.1 優勢

  Koa的一個小進步就是他的代碼比較富有表現力,開發中間件也比其他框架更容易得多。Koa是一個很基礎的准系統框架,開發者可以選擇(或開發)他們所需要的中間件,而不是去選擇Express或Hapi的中間件。他同時也是三者中唯一一個積極擁抱ES6的框架,比如采用了ES6 generators函數。

5.2.2 劣勢

  目前Koa還處於不穩定版本,還處在開發階段。使用ES6進行開發的確是處於領先水平,比如Koa需要基於Nodejs 0.11.9以上的版本運行,而目前nodejs的文本版本是0.10.33。這是一件可以算作好也可以算作不好的事情,就像Express開發者有很多中間件要選擇甚至自己開發中間件一樣。比如我們在上面看到的一樣,對於路由來說就有很多中間件供我們選擇。

5.3 Hapi

5.3.1 優勢

  Hapi一直很自豪的說他們的框架是配置優於代碼,當然也有很多開發者可能會質疑把這一點算作是優勢。但這一點對於大型項目組來說,的確是可以保持代碼的統一性以及代碼復用性。另外這款框架是由沃爾瑪實驗室支持的,也有很多大公司在線上環境使用Hapi,表明他已經通過了嚴峻的測試,因為這些公司會考慮得更多才會使用Hapi來運行他們的項目。因此所有的這些跡象都表明Hapi正在朝一個偉大的框架發展。

5.3.2 劣勢

  Hapi的定位更傾向於大型或復雜的應用程序。對於一個簡單的應用來說,Hapi在代碼上反而有些顯得冗余了,另外目前Hapi所提供的樣例程序也比較少,使用Hapi進行開發的開源應用同樣很少。因此,如果選擇Hapi的話,你可能要投入更多精力進行開發,而不是簡單的調用一個第三方中間件。

6、總結

  我們已經看了三個框架還算不錯具有代表性的一些樣例代碼。Express仍然是當下最為流行,以及最被人所知曉的框架。當開始一個新的開發項目時,可能大家的第一反應就是用Express來創建一個服務。但是現在更希望大家多考慮考慮使用Koa或者Hapi。Koa積極擁抱ES6的語法,展示了promise的真正魅力。目前整個web開發社區也都意識到ES6的優勢,正在逐步往上面遷移。Hapi應該是大型項目組或者大型項目的第一選擇。他所倡導的配置優於代碼會使得項目組 在代碼的重復性上受益不淺,這也正是大多數項目組所追求的目標。現在行動起來,嘗試一款新的框架吧,可能你會喜歡他也可能會討厭他,但如果不去嘗試你永遠也不會知道結果是什麼,最終所有的這些經歷都會讓你成長為一個更加優秀的開發者。

Copyright © Linux教程網 All Rights Reserved