Как экспресс и хапи сравниваются друг с другом?


133

Чем отличаются Express и Hapi с точки зрения дизайна и разработки веб-приложений? Для базовых примеров они кажутся похожими, однако мне интересно узнать больше о ключевых различиях в общей структуре приложения.

Например, насколько я узнал, Hapi использует другой механизм маршрутизации, который не принимает во внимание порядок регистрации, может выполнять более быстрый поиск, но ограничен по сравнению с Express. Есть ли другие важные отличия?

Также есть статья о выборе Hapi (вместо Express) для разработки нового веб-сайта npmjs.com, в этой статье говорится, что «система плагинов Hapi означает, что мы можем изолировать различные аспекты и службы приложения способами, которые позволят использовать микросервисы в future. Express, с другой стороны, требует немного дополнительных настроек, чтобы получить ту же функциональность », что именно это означает?

Ответы:


231

Это большой вопрос, и для его полного ответа требуется длинный ответ, поэтому я остановлюсь только на некоторых наиболее важных различиях. Извините, что это все еще длинный ответ.

Чем они похожи?

Вы абсолютно правы, когда говорите:

Для базовых примеров они кажутся похожими

Обе структуры решают одну и ту же основную проблему: предоставление удобного API для построения HTTP-серверов в node. Другими словами, это удобнее, чем использовать только собственный httpмодуль нижнего уровня . httpМодуль может сделать все , что мы хотим , но это утомительно для приложений записи с.

Для этого они оба используют концепции, которые уже давно используются в веб-фреймворках высокого уровня: маршрутизация, обработчики, плагины, модули аутентификации. Возможно, у них не всегда были одинаковые имена, но они примерно эквивалентны.

Большинство базовых примеров выглядят примерно так:

  • Создать маршрут
  • Запустить функцию при запросе маршрута, подготовив ответ
  • Ответить на запрос

Экспресс:

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 - это намного больше, и он делает намного больше из коробки. Это может быть неясно, если вы просто посмотрите на простой пример сверху. На самом деле это сделано намеренно. Простые случаи остаются простыми. Итак, давайте рассмотрим некоторые из основных различий:

философия

Экспресс задуман как минимум. Предоставляя вам небольшой API с тонкой пылью поверх http, вы по-прежнему сами по себе с точки зрения добавления дополнительных функций. Если вы хотите прочитать тело входящего запроса (довольно частая задача), вам необходимо установить отдельный модуль . Если вы ожидаете, что по этому маршруту будут отправлены различные типы контента, вам также необходимо проверить Content-typeзаголовок, чтобы проверить, что это за заголовок, и проанализировать его соответствующим образом (например, данные формы или JSON или составные части), часто с использованием отдельных модулей. ,

hapi имеет богатый набор функций, который часто предоставляется через параметры конфигурации, а не требует написания кода. Например, если мы хотим убедиться, что тело запроса (полезная нагрузка) полностью считывается в память и должным образом анализируется (автоматически в зависимости от типа содержимого) перед запуском обработчика, это просто вариант :

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, было разочарование тем, насколько сложно было разделить приложение Express на отдельные части и заставить разных членов команды безопасно работать над их фрагментом. По этой причине они создали систему плагинов в hapi.

Плагин похож на суб-приложение, вы можете делать все, что можете в приложении hapi, добавлять маршруты, точки расширения и т. Д. В плагине вы можете быть уверены, что не нарушаете другую часть приложения, потому что порядок регистрация маршрутов не имеет значения, и вы не можете создавать конфликтующие маршруты. Затем вы можете объединить эти плагины в сервер и развернуть его.

экосистема

Поскольку Express дает вам так мало из коробки, вам нужно смотреть вовне, когда вам нужно что-то добавить в свой проект. Часто при работе с hapi функция, которая вам нужна, либо встроена, либо есть модуль, созданный основной командой.

Минимум звучит отлично. Но если вы создаете серьезное производственное приложение, скорее всего, в конечном итоге все это вам понадобится.

Безопасность

hapi был разработан командой Walmart для управления трафиком в Черную пятницу, поэтому безопасность и стабильность всегда были главной заботой. По этой причине фреймворк выполняет множество дополнительных функций, таких как ограничение размера входящей полезной нагрузки, чтобы предотвратить исчерпание памяти вашего процесса. Он также имеет параметры для таких вещей, как максимальная задержка цикла событий, максимальная используемая память RSS и максимальный размер кучи v8, после которых ваш сервер будет отвечать тайм-аутом 503, а не просто сбой.

Резюме

Оцените их обоих самостоятельно. Подумайте о своих потребностях и о том, какой из двух вопросов решает ваши самые большие проблемы. Окунитесь в два сообщества (IRC, Gitter, Github), посмотрите, какое вы предпочитаете. Не верьте мне на слово. И счастливого взлома!


ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: как автор книги о хапи я пристрастен, и вышесказанное в значительной степени является моим личным мнением.


7
Мэтт, спасибо за обширный пост, разделы «расширяемость и модульность» и «безопасность» были для меня самыми полезными разделами. Думаю, стоит упомянуть, что новая система маршрутизации в Express 4 обеспечивает улучшенную модульность для субприложений.
Али Шакиба

1
Отличный ответ, Мэтт. Мы также запутались в ч / б Hapi и Express, один недостаток, который мы видим с Hapi, заключается в том, что он не имеет такой обширной поддержки сообщества, как Express, и может стать серьезной проблемой, если мы где-то застрянем. Нужно ваше мнение по поводу того же.
Аман Гупта,

1
Express является общим, а hapi - более корпоративным.
windmaomao

1
@MattHarrison отличный ответ, прямо сейчас я читаю вашу книгу о Хапи, это просто здорово. Я собираюсь разработать новую торговую площадку для книг с использованием Hapi на бэкэнде и vue.js во фронтенде, после того, как я привык к Hapi, я хотел бы активно участвовать в проекте Hapi.
Humoyun Ahmad

1
@Humoyun Отлично! Однако имейте в виду, что существует новая основная версия hapi с некоторыми существенными изменениями, начиная с версии <= v16.0.0. В настоящее время я создаю серию скринкастов, предназначенную для людей, желающих изучить версию 17: youtube.com/playlist?list=PLi303AVTbxaxqjaSWPg94nccYIfqNoCHz
Мэтт Харрисон

54

Моя организация работает с Хапи. Вот почему нам это нравится.

Хапи это:

  • При поддержке основного корпуса. Это означает, что поддержка сообщества будет сильной и будет доступна вам в будущих выпусках. Легко найти увлеченных хапи людей, и есть хорошие учебные пособия (хотя и не такие многочисленные и обширные, как учебные пособия по ExpressJ). На момент публикации npm и Walmart используют Hapi.
  • Он может облегчить работу распределенных команд, работающих над различными частями серверных сервисов, без необходимости иметь всесторонние знания об остальной поверхности API (архитектура плагинов Hapi является воплощением этого качества).
  • Позвольте фреймворку делать то, что он должен: настраивать. После этого фреймворк должен быть невидимым и позволить разработчикам сосредоточить свою настоящую творческую энергию на построении бизнес-логики. После года использования Hapi я определенно чувствую, что Hapi добивается этого. Я счастлив!

Если вы хотите услышать напрямую от Эрана Хаммера (ведущая роль Хапи)

За последние четыре года хапи превратилась в основу выбора для многих проектов, больших и малых. Что делает hapi уникальным, так это его способность масштабироваться для крупных развертываний и больших команд. По мере роста проекта растет и его сложность - инженерная сложность и сложность процесса. Архитектура и философия hapi позволяют справиться с возросшей сложностью без необходимости постоянного рефакторинга кода [подробнее]

Начать работу с Hapi будет не так просто, как с ExpressJs, потому что у Hapi нет такой «звездной силы» ... но как только вы почувствуете себя комфортно, вы получите МНОГО пробега. Мне потребовалось около 2 месяцев в качестве нового хакера, который безответственно использовал ExpressJ в течение нескольких лет. Если вы опытный backend-разработчик, вы знаете, как читать документацию, и, вероятно, даже не заметите этого.

Области, в которых документация Hapi может улучшить:

  1. как аутентифицировать пользователей и создавать сеансы
  2. обработка запросов Cross-Origin-Request (CORS)
  3. загрузка файлов (составных, фрагментированных)

Я думаю, что аутентификация будет самой сложной частью этого, потому что вам нужно решить, какую стратегию аутентификации использовать (базовая аутентификация, файлы cookie, токены JWT, OAuth). Хотя технически проблема Hapi не в том, что среда сеансов / аутентификации настолько фрагментирована ... но я действительно хочу, чтобы они помогли в этом. Это значительно увеличило бы счастье разработчиков.

Остальные два на самом деле не так уж и сложны, просто документы можно было бы написать немного лучше.


3

Факты о Hapi или почему Hapi JS?

Hapi ориентирован на конфигурацию. Он имеет встроенную аутентификацию и авторизацию. Он был выпущен в испытанной боевой обстановке и действительно доказал свою ценность. Все модули имеют 100% тестовое покрытие. Он регистрирует высочайший уровень абстракции вдали от ядра HTTP. через архитектуру плагина

Hapi - лучший выбор с точки зрения производительности. Hapi использует другой механизм маршрутизации, который может выполнять более быстрый поиск и учитывать порядок регистрации. Тем не менее, он довольно ограничен по сравнению с Express. А благодаря системе плагинов Hapi можно изолировать различные аспекты и сервисы, которые во многом помогут приложению в будущем.

использование

Hapi - наиболее предпочтительный фреймворк по сравнению с Express. Hapi используется в основном для крупномасштабных корпоративных приложений.

Вот несколько причин, по которым разработчики не выбирают Express при создании корпоративных приложений:

Маршруты сложнее составлять в Express

ПО промежуточного слоя большую часть времени мешает; каждый раз, когда вы определяете маршруты, вы должны писать столько кодов.

Hapi будет лучшим выбором для разработчика, желающего создать RESTful API. Hapi имеет архитектуру микросервисов, и также можно передавать управление от одного обработчика к другому на основе определенных параметров. С плагином Hapi вы можете получить более высокий уровень абстракции вокруг HTTP, потому что вы можете разделить бизнес-логику на части, которыми легко управлять.

Еще одно огромное преимущество Hapi заключается в том, что при неправильной настройке он выдает подробные сообщения об ошибках. Hapi также позволяет настроить размер загружаемого файла по умолчанию. Если максимальный размер загрузки ограничен, вы можете отправить пользователю сообщение об ошибке, сообщающее, что размер файла слишком велик. Это защитит ваш сервер от сбоя, потому что загрузка файлов больше не будет пытаться буферизовать весь файл.

  1. Все, чего вы можете достичь с помощью экспресса, также легко можно сделать с помощью hapi.js.

  2. Hapi.js очень стильный и отлично организует код. Если вы увидите, как он выполняет маршрутизацию и помещает основную логику в контроллеры, вам это наверняка понравится.

  3. Hapi.js официально предоставляет несколько плагинов исключительно для hapi.js, от аутентификации на основе токенов до управления сеансами и многого другого, что является рекламой. Это не значит, что нельзя использовать традиционный npm, все они поддерживаются hapi.js.

  4. Если вы кодируете в hapi.js, код будет очень легко поддерживать.


«Если вы посмотрите, как он выполняет маршрутизацию и помещает основную логику в контроллеры ...». Я не вижу в документации примеров, показывающих использование контроллеров. Во всех примерах маршрутизации используется свойство обработчика, которое является функцией. Я сравниваю этот способ с тем, что Laravel (инфраструктура PHP) и AdonisJs (платформа Node.js) делают для маршрутизации, в которой мы можем использовать контроллеры для маршрутизации. Я, вероятно, пропустил части документа HAPI, которые показывают использование контроллеров для маршрутизации. Так что, если эта функция действительно существует, мне будет хорошо, потому что я привык использовать контроллеры для маршрутизации в Laravel.
Lex Soft,

1

Я начал использовать Hapi недавно, и меня это вполне устраивает. Мои причины

  1. Легче проверить. Например:

    • server.inject позволяет запустить приложение и получить ответ без его запуска и прослушивания.
    • server.info дает текущий uri, порт и т. д.
    • server.settingsполучает доступ к конфигурации, например, server.settings.cacheполучает текущий поставщик кеша
    • в случае сомнений просмотрите /testпапки для любой части приложения или поддерживаемых плагинов, чтобы увидеть предложения о том, как имитировать / тестировать / заглушить и т. д.
    • Я считаю, что архитектурная модель hapi позволяет вам доверять, но проверять, например. Зарегистрированы ли мои плагины ? Как я могу объявить зависимость модуля ?
  2. Он работает из коробки, например, загрузка файлов , возврат потоков с конечных точек и т. Д.

  3. Основные плагины поддерживаются вместе с основной библиотекой. например, синтаксический анализ шаблонов , кеширование и т. д. Дополнительным преимуществом является то, что одни и те же стандарты кодирования применяются ко всем основным вещам.

  4. Вменяемые ошибки и обработка ошибок. Hapi проверяет параметры конфигурации и ведет внутреннюю таблицу маршрутов для предотвращения дублирования маршрутов. Это очень полезно при обучении, потому что ошибки возникают раньше, чем неожиданное поведение, требующее отладки.


-1

Еще одно замечание: Hapi начал поддерживать вызовы http2 начиная с версии 16 (если я не ошибаюсь). Однако Express еще не поддерживает модуль http2 напрямую до Express 4. Хотя они выпустили эту функцию в альфа-версии Express 5.


-2
'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
    }
}

Добро пожаловать в StackOverflow. Не могли бы вы подробнее рассказать о своем ответе и о том, как он соотносится с вопросом, заданным OP?
Szymon Maszke
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.