Webアプリケーションの設計と開発の観点から、ExpressとHapiはどのように比較されますか?基本的な例については似ていますが、アプリケーション構造全体の主な違いについて詳しく知りたいと思います。
たとえば、私が学んだ限りでは、Hapiは different ルーティングメカニズムを使用します。これは登録順序を考慮せず、より高速なルックアップを実行できますが、Expressと比較して制限されています。他に重要な違いはありますか?
記事 新しいnpmjs.com Webサイトを開発するためのHapi(エクスプレス経由)の選択についてもあります。一方、Expressでは、同じ機能を実現するためにもう少し設定が必要です」と、正確にはどういう意味ですか?
これは大きな質問であり、完了するには長い回答が必要なので、最も重要な相違点のサブセットに対処します。それはまだ長い答えであることをおologiesびします。
あなたが言うとき、あなたは絶対に正しいです:
基本的な例については、それらは似ているようです
どちらのフレームワークも同じ基本的な問題を解決しています。ノードにHTTPサーバーを構築するための便利なAPIを提供します。つまり、下位レベルのネイティブ http
モジュールのみを使用するよりも便利です。 http
モジュールは、私たちが望むすべてを実行できますが、アプリケーションを書くのは面倒です。
これを実現するために、彼らは両方とも長い間、高レベルのWebフレームワークに存在していた概念を使用しています:ルーティング、ハンドラー、プラグイン、認証モジュール。彼らは常に同じ名前を持っていなかったかもしれませんが、それらはほぼ同等です。
基本的な例のほとんどは次のようになります。
エクスプレス:
app.get('/', function (req, res) {
getSomeValue(function (obj) {
res.json({an: 'object'});
});
});
ハピ:
server.route({
method: 'GET',
path: '/',
handler: function (request, reply) {
getSomeValue(function (obj) {
reply(obj);
});
}
});
違いはまさにここで画期的なものではありませんか?それでは、なぜ一方を選択するのですか?
簡単な答えは、hapiの方がはるかに多く、すぐに使えることです。上記の簡単な例を見るだけでは、はっきりしないかもしれません。実際、これは意図的なものです。シンプルなケースはシンプルに保たれます。それでは、いくつかの大きな違いを調べてみましょう。
Expressは、最小限にすることを目的としています。 http
の上にほんのわずかな埃を塗っただけの小さなAPIを提供することで、追加の機能を追加するという点であなたはまだ独力です。着信リクエストの本文を読みたい場合(一般的なタスクをやめる)、 separate module をインストールする必要があります。さまざまなコンテンツタイプがそのルートに送信されることを期待している場合は、Content-type
ヘッダーもチェックして、それを確認し、それに応じて解析する必要があります(たとえば、フォームデータvs JSON vsマルチパート) 、しばしば別々のモジュールを使用します。
hapiには豊富な機能セットがあり、多くの場合、構成オプションを介して公開され、コードを記述する必要はありません。たとえば、ハンドラを実行する前に、リクエスト本文(ペイロード)がメモリに完全に読み込まれ、適切に解析される(コンテンツタイプに基づいて自動的に)ことを確認する場合は、単純な option です。
server.route({
config: {
payload: {
output: 'data',
parse: true
}
},
method: 'GET',
path: '/',
handler: function (request, reply) {
reply(request.payload);
}
});
両方のプロジェクトのAPIドキュメントを比較するだけで、hapiがより大きな機能セットを提供することがわかります。
hapiには、Expressにはない次の機能が組み込まれています(私の知る限り)。
hapiとExpressは、まったく異なる方法で拡張性を実現します。 Expressでは、 ミドルウェア 関数があります。ミドルウェア関数は、ユーザーがスタックするフィルターのようなもので、ハンドラーにヒットする前にすべての要求がそれらを通過します。
hapiには リクエストライフサイクル があり、 拡張ポイント を提供します。これはミドルウェア機能に匹敵しますが、リクエストライフサイクルにはいくつかの定義されたポイントが存在します。
Walmartがhapiを構築し、Expressの使用を停止した理由の1つは、Expressアプリを個別の部分に分割し、異なるチームメンバーが自分のチャンクで安全に作業することの難しさに対するフラストレーションです。このため、彼らは plugin system をhapiで作成しました。
プラグインはサブアプリケーションのようなもので、ハピアプリでできることはすべて実行でき、ルートや拡張ポイントなどを追加できます。プラグインでは、アプリケーションの別の部分を壊していないことを確認できます。ルートの登録は重要ではなく、競合するルートを作成することはできません。その後、このプラグインをサーバーに結合して展開できます。
Expressにはすぐに使用できるものがほとんどないため、プロジェクトに何かを追加する必要がある場合は外を見る必要があります。 hapiを使用する場合、多くの場合、必要な機能は組み込みであるか、コアチームによって作成されたモジュールがあります。
最小限の音が素晴らしい。しかし、本格的なプロダクションアプリを構築している場合は、最終的にはこれらすべてを必要とする可能性があります。
hapiは、ブラックフライデートラフィックを実行するようにウォルマートのチームによって設計されたため、セキュリティと安定性が常に最大の関心事でした。このため、フレームワークは、プロセスメモリの枯渇を防ぐために受信ペイロードサイズを制限するなど、多くの特別なことを行います。また、最大イベントループ遅延、使用される最大RSSメモリ、v8ヒープの最大サイズなどのオプションがあり、それを超えると、サーバーは単にクラッシュするのではなく、503タイムアウトで応答します。
両方を自分で評価してください。あなたのニーズと、どちらがあなたの最大の懸念に対処するかを考えてください。 2つのコミュニティ(IRC、Gitter、Github)でひと泳ぎして、どちらが好きか見てみましょう。私の言葉だけではありません。そしてハッピーハッキング!
免責事項:私は hapiの本 の著者として偏見があり、上記は主に私の個人的な意見です。
私の組織はHapiを使用しています。これが私たちが好きな理由です。
ハピは:
Eran Hammer(Hapiのリード)から直接聞きたい場合
過去4年間で、hapiは、大小を問わず多くのプロジェクトの選択肢のフレームワークに成長しました。 hapiのユニークな点は、大規模な展開と大規模なチームに対応できることです。プロジェクトが成長するにつれて、その複雑さ、つまりエンジニアリングの複雑さとプロセスの複雑さが増します。 hapiのアーキテクチャと哲学は、コードを絶えずリファクタリングする必要なく、複雑さの増大を処理します [続きを読む]
Hapiには同じ「スターパワー」がないので、Hapiを使い始めるのはExpressJsほど簡単ではありません...しかし、快適に感じたら、たくさんのマイルを獲得できます。 ExpressJを数年間無責任に使用した新しいハッカーとして約2か月かかりました。あなたがベテランのバックエンド開発者であれば、ドキュメントの読み方を知っているでしょうし、おそらくこれに気付かないでしょう。
Hapiドキュメントが改善できる領域:
どの種類の認証戦略(基本認証、Cookie、JWTトークン、OAuth)を使用するかを決定する必要があるため、認証は最も難しい部分だと思います。技術的にはHapiの問題ではありませんが、セッション/認証の状況が非常に断片化されているのですが...開発者の幸福度が大幅に向上します。
残りの2つは実際にはそれほど難しくありませんが、ドキュメントを少しだけ改善することができます。
私は最近Hapiを使い始めましたが、とても満足しています。私の理由は
テストが簡単。例えば:
server.inject
を使用すると、アプリを実行し、実行およびリスニングせずに応答を取得できます。server.info
は、現在のURI、ポートなどを提供します。server.settings
設定にアクセスします。 server.settings.cache
は現在のキャッシュプロバイダーを取得します/test
フォルダーを見て、モック/テスト/スタブなどの方法に関する提案を確認してください。それは箱から出して動作します ファイルのアップロード 、エンドポイントなどからストリームを返します。
必須のプラグインは、コアライブラリとともに維持されます。例: テンプレート解析 、 キャッシュ など。追加の利点は、同じコーディング標準が重要なものに適用されることです。
健全なエラーとエラー処理。 Hapi 設定オプションの検証 。内部ルートテーブルを保持して、ルートの重複を防ぎます。これは、デバッグが必要な予期しない動作の代わりにエラーが早期にスローされるため、学習中に非常に役立ちます。
Hapiに関する簡単な事実またはなぜHapi JS?
Hapiは構成中心であり、フレームワークに認証と承認が組み込まれています。バトルテスト済みの環境でリリースされ、その価値が実証されています。すべてのモジュールが100%テストカバレッジを持ちます。プラグインアーキテクチャ経由
Hapiはパフォーマンスの点で優れた選択肢です。Hapiは別のルーティングメカニズムを使用します。これにより、検索が高速になり、登録順序を考慮することができます。それにもかかわらず、Expressと比較すると、かなり制限されています。また、Hapiプラグインシステムのおかげで、将来さまざまな方法でアプリケーションを支援するさまざまなファセットとサービスを分離できます。
使用法
Hapiは、Expressと比較した場合に最も好ましいフレームワークです。 Hapiは、主に大規模なエンタープライズアプリケーションに使用されます。
開発者がエンタープライズアプリケーションの作成時にExpressを選択しない理由はいくつかあります。
Expressでルートを作成するのが難しい
ミドルウェアはほとんどの場合邪魔になります。ルートを定義するたびに、できるだけ多くのコードを記述する必要があります。
Hapiは、RESTful APIを構築しようとしている開発者にとって最良の選択です。 Hapiにはマイクロサービスアーキテクチャがあり、特定のパラメーターに基づいて1つのハンドラーから別のハンドラーに制御を転送することもできます。 Hapiプラグインを使用すると、ビジネスロジックを簡単に管理できる部分に分割できるため、HTTPをより高度に抽象化できます。
Hapiのもう1つの大きな利点は、設定を誤ったときに詳細なエラーメッセージが表示されることです。 Hapiでは、デフォルトでファイルのアップロードサイズを構成することもできます。最大アップロードサイズが制限されている場合は、ファイルサイズが大きすぎることを伝えるエラーメッセージをユーザーに送信できます。これにより、ファイルのアップロードでファイル全体のバッファリングが試行されなくなるため、サーバーがクラッシュするのを防ぐことができます。
Expressを使用して達成できることはすべて、hapi.jsを使用しても簡単に達成できます。
Hapi.jsは非常にスタイリッシュで、コードを非常にうまく整理します。それがどのようにルーティングを行い、コントローラにコアロジックを配置するかを見ると、間違いなくそれを好きになるでしょう。
Hapi.jsは、トークンベースの認証からセッション管理など、広告であるhapi.js専用のプラグインを公式に提供しています。これは、従来のnpmが使用できないことを意味するものではなく、それらはすべてhapi.jsによってサポートされています
Hapi.jsでコーディングすると、コードは非常に保守しやすくなります。
'use strict';
const Hapi = require('hapi');
const Basic = require('hapi-auth-basic');
const server = new Hapi.Server();
server.connection({
port: 2090,
Host: 'localhost'
});
var vorpal = require('vorpal')();
const chalk = vorpal.chalk;
var fs = require("fs");
var utenti = [{
name: 'a',
pass: 'b'
},
{
name: 'c',
pass: 'd'
}
];
const users = {
john: {
username: 'john',
password: 'secret',
name: 'John Doe',
id: '2133d32a'
},
paul: {
username: 'paul',
password: 'password',
name: 'Paul Newman',
id: '2133d32b'
}
};
var messaggi = [{
destinazione: 'a',
sorgente: 'c',
messsaggio: 'ciao'
},
{
destinazione: 'a',
sorgente: 'c',
messsaggio: 'addio'
},
{
destinazione: 'c',
sorgente: 'a',
messsaggio: 'arrivederci'
}
];
var login = '';
var loggato = false;
vorpal
.command('login <name> <pass>')
.description('Effettua il login al sistema')
.action(function (args, callback) {
loggato = false;
utenti.forEach(element => {
if ((element.name == args.name) && (element.pass == args.pass)) {
loggato = true;
login = args.name;
console.log("Accesso effettuato");
}
});
if (!loggato)
console.log("Login e Password errati");
callback();
});
vorpal
.command('leggi')
.description('Leggi i messaggi ricevuti')
.action(function (args, callback) {
if (loggato) {
var estratti = messaggi.filter(function (element) {
return element.destinazione == login;
});
estratti.forEach(element => {
console.log("mittente : " + element.sorgente);
console.log(chalk.red(element.messsaggio));
});
} else {
console.log("Devi prima loggarti");
}
callback();
});
vorpal
.command('invia <dest> "<messaggio>"')
.description('Invia un messaggio ad un altro utente')
.action(function (args, callback) {
if (loggato) {
var trovato = utenti.find(function (element) {
return element.name == args.dest;
});
if (trovato != undefined) {
messaggi.Push({
destinazione: args.dest,
sorgente: login,
messsaggio: args.messaggio
});
console.log(messaggi);
}
} else {
console.log("Devi prima loggarti");
}
callback();
});
vorpal
.command('crea <login> <pass>')
.description('Crea un nuovo utente')
.action(function (args, callback) {
var trovato = utenti.find(function (element) {
return element.name == args.login;
});
if (trovato == undefined) {
utenti.Push({
name: args.login,
pass: args.pass
});
console.log(utenti);
}
callback();
});
vorpal
.command('file leggi utenti')
.description('Legge il file utenti')
.action(function (args, callback) {
var contents = fs.readFileSync("utenti.json");
utenti = JSON.parse(contents);
callback();
});
vorpal
.command('file scrivi utenti')
.description('Scrive il file utenti')
.action(function (args, callback) {
var jsontostring = JSON.stringify(utenti);
fs.writeFile('utenti.json', jsontostring, function (err) {
if (err) {
return console.error(err);
}
});
callback();
});
vorpal
.command('file leggi messaggi')
.description('Legge il file messaggi')
.action(function (args, callback) {
var contents = fs.readFileSync("messaggi.json");
messaggi = JSON.parse(contents);
callback();
});
vorpal
.command('file scrivi messaggi')
.description('Scrive il file messaggi')
.action(function (args, callback) {
var jsontostring = JSON.stringify(messaggi);
fs.writeFile('messaggi.json', jsontostring, function (err) {
if (err) {
return console.error(err);
}
});
callback();
});
// leggi file , scrivi file
vorpal
.delimiter(chalk.yellow('messaggi$'))
.show();
const validate = function (request, username, password, callback) {
loggato = false;
utenti.forEach(element => {
if ((element.name == username) && (element.pass == password)) {
loggato = true;
console.log("Accesso effettuato");
return callback(null, true, {
name: username
})
}
});
if (!loggato)
return callback(null, false);
};
server.register(Basic, function (err) {
if (err) {
throw err;
}
});
server.auth.strategy('simple', 'basic', {
validateFunc: validate
});
server.route({
method: 'GET',
path: '/',
config: {
auth: 'simple',
handler: function (request, reply) {
reply('hello, ' + request.auth.credentials.name);
}
}
});
//route scrivere
server.route({
method: 'POST',
path: '/invia',
config: {
auth: 'simple',
handler: function (request, reply) {
//console.log("Received POST from " + request.payload.name + "; id=" + (request.payload.id || 'anon'));
var payload = encodeURIComponent(request.payload)
console.log(request.payload);
console.log(request.payload.dest);
console.log(request.payload.messaggio);
messaggi.Push({
destinazione: request.payload.dest,
sorgente: request.auth.credentials.name,
messsaggio: request.payload.messaggio
});
var jsontostring = JSON.stringify(messaggi);
fs.writeFile('messaggi.json', jsontostring, function (err) {
if (err) {
return console.error(err);
}
});
console.log(messaggi);
reply(messaggi[messaggi.length - 1]);
}
}
});
//route leggere (json)
server.route({
method: 'GET',
path: '/messaggi',
config: {
auth: 'simple',
handler: function (request, reply) {
messaggi = fs.readFileSync("messaggi.json");
var estratti = messaggi.filter(function (element) {
return element.destinazione == request.auth.credentials.name;
});
var s = [];
console.log(request.auth.credentials.name);
console.log(estratti.length);
estratti.forEach(element => {
s.Push(element);
//fare l'array con stringify
//s+="mittente : "+element.sorgente+": "+element.messsaggio+"\n";
});
var a = JSON.stringify(s);
console.log(a);
console.log(s);
reply(a);
}
}
});
server.start(function () {
console.log('Hapi is listening to ' + server.info.uri);
});
function EseguiSql(connection, sql, reply) {
var rows = [];
request = new Request(sql, function (err, rowCount) {
if (err) {
console.log(err);
} else {
console.log(rowCount + ' rows');
console.log("Invio Reply")
reply(rows);
}
});
request.on('row', function (columns) {
var row = {};
columns.forEach(function (column) {
row[column.metadata.colName] = column.value;
});
rows.Push(row);
});
connection.execSql(request);
}
server.route({
method: 'POST',
path: '/query',
handler: function (request, reply) {
// Qui dovrebbe cercare i dati nel body e rispondere con la query eseguita
var connection = new Connection(config);
// Attempt to connect and execute queries if connection goes through
connection.on('connect', function (err) {
if (err) {
console.log(err);
} else {
console.log('Connected');
console.log(request.payload.sql);
EseguiSql(connection, request.payload.sql, reply);
}
});
}
});
server.connection({
Host: process.env.Host || 'localhost',
port: process.env.PORT || 8080
});
var config = {
userName: process.env.DB_USER,
password: process.env.DB_PASSWORD,
server: process.env.DB_SERVER,
options: {
database: process.env.DB_NAME,
encrypt: true
}
}
追加するもう1つのポイントは、Hapiがバージョン16以降の「http2」呼び出しのサポートを開始したことです(私が間違っていない場合)。ただし、ExpressはExpress 4まで「http2」モジュールを直接サポートしていません。Express5のアルファ版の機能はリリースされていますが。
const Hapi = require('hapi');
var Connection = require('tedious').Connection;
var Request = require('tedious').Request;
var TYPES = require('tedious').TYPES;
const server = new Hapi.Server();
var vorpal = require('vorpal')();
server.connection({
Host: process.env.Host || 'localhost',
port: process.env.PORT || 3000
});
server.start(function (err) {
if (err) {
throw err;
}
console.log("server running at : " + server.info.uri);
});
var config =
{
userName: 'sa',
password: 'password.123',
server: 'localhost',
options:
{
database: '',
port: 1433
}
}
server.route(
{
method: 'GET',
path: '/{categoria}',
handler: function (request, reply) {
var connection = new Connection(config);
connection.on('connect', function (err) {
if (err) {
console.log(err);
}
else {
console.log('Connected');
EseguiSqlGet(connection, request.params.categoria, reply);
}
});
}
}
);
function EseguiSqlGet(connection, cat, reply) {
var rows = [];
var sql = 'SELECT * FROM Prodotti INNER JOIN Categorie
on Categorie.IdCategoria = Prodotti.IdCategoria
WHERE Categorie.IdCategoria = ' + cat ;
request_sql = new Request(sql, function(err, rowCount) {
if (err) {
console.log(err);
} else {
console.log(rowCount + ' rows');
console.log("Invio Reply")
reply(rows);
}
});
request_sql.on('row', function(columns) {
var row = {};
columns.forEach(function (column) {
row[column.metadata.colName] = column.value;
});
rows.Push(row);
});
connection.execSql(request_sql);
}
// POST
server.route(
{
method: 'POST',
path: '/inserisci',
handler: function (request, reply) {
var connection = new Connection(config);
connection.on('connect', function (err) {
if (err) {
console.log(err);
}
else {
console.log('Connected');
EseguiSqlPost(connection,reply,
request.payload.idcat, request.payload.nome, request.payload.prezzo );
}
});
}
}
);
function EseguiSqlPost(connection,reply, cat,nome,prezzo) {
var sql = "INSERT INTO Prodotti
VALUES("+ cat +",'"+nome+"',"+prezzo+")";
request_sql = new Request(sql, function(err, rowCount) {
if (err) {
console.log(err);
} else {
console.log(rowCount + ' rows');
console.log("Invio Reply")
reply('riga aggiunta');
}
});
/*request_sql.on('row', function(columns) {
var row = {};
columns.forEach(function (column) {
row[column.metadata.colName] = column.value;
});
rows.Push(row);
});
*/
connection.execSql(request_sql);
}
//VORPAL COMMAND PROMT
var categoria = [
{
'idcategoria':'1',
'nome':'ciao',
}
]
vorpal
.command('inserisci <categoria> <nome>')
.action(function(args, callback)
{
categoria.Push(
{'idcategoria':args.categoria,'nome':args.nome} );
console.log(JSON.stringify(categoria));
callback();
});
vorpal
.delimiter("delimeter")
.show();