[node.js] Express와 hapi는 어떻게 다른가요?

웹 애플리케이션 디자인 및 개발 관점에서 Express와 Hapi는 어떻게 비교됩니까? 기본 예제의 경우 비슷해 보이지만 전반적인 응용 프로그램 구조의 주요 차이점에 대해 자세히 알고 싶습니다.

예를 들어, 내가 아는 한, Hapi는 등록 순서를 고려하지 않고 더 빠른 조회를 수행 할 수 있지만 Express에 비해 제한적인 다른 라우팅 메커니즘을 사용합니다 . 다른 중요한 차이점이 있습니까?

새로운 npmjs.com 웹 사이트를 개발하기 위해 Hapi (Express를 통해)를 선택하는 방법에 대한 기사 도 있습니다.이 기사에서는 “Hapi의 플러그인 시스템은 마이크로 서비스를 지원할 수있는 방식으로 애플리케이션의 다양한 측면과 서비스를 분리 할 수 ​​있음을 의미합니다. 반면에 Express는 동일한 기능을 얻기 위해 조금 더 많은 구성이 필요합니다. “정확히 무엇을 의미합니까?



답변

이것은 큰 질문이며 완성되기 위해서는 긴 답변이 필요하므로 가장 중요한 차이점 중 일부만 다루겠습니다. 여전히 긴 답변이라고 사과합니다.

그것들은 어떻게 비슷합니까?

당신이 말할 때 당신은 절대적으로 옳습니다 :

기본 예제의 경우 비슷해 보입니다.

두 프레임 워크 모두 동일한 기본 문제를 해결합니다. 노드에 HTTP 서버를 빌드하기위한 편리한 API 제공. 즉, 하위 레벨 기본 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가 훨씬 더 많으며 기본적으로 훨씬 더 많은 기능을 수행한다는 것입니다. 위에서 간단한 예제를 볼 때 명확하지 않을 수 있습니다. 실제로 이것은 의도적 인 것입니다. 간단한 경우는 단순하게 유지됩니다. 따라서 몇 가지 큰 차이점을 살펴 보겠습니다.

철학

Express는 매우 최소화되도록 고안되었습니다. 에 약간의 먼지를 뿌리는 작은 API를 제공 http함으로써 추가 기능을 추가하는 측면에서 여전히 많은 부분을 차지합니다. 들어오는 요청의 본문을 읽으려면 (공통 작업) 별도의 모듈 을 설치해야 합니다 . 다양한 콘텐츠 유형이 해당 경로로 전송 될 것으로 예상되는 경우 Content-type헤더를 확인 하여 헤더를 확인하고 그에 따라 구문 분석해야합니다 (예 : 양식 데이터 대 JSON 대 다중 부분). .

hapi에는 코드를 작성하지 않고 구성 옵션을 통해 노출되는 다양한 기능 세트가 있습니다. 예를 들어, 핸들러가 실행되기 전에 요청 본문 (페이로드)이 메모리로 완전히 읽혀지고 적절하게 구문 분석 (콘텐츠 유형에 따라 자동으로)되도록하려면 간단한 옵션입니다 .

server.route({
    config: {
        payload: {
            output: 'data',
            parse: true
        }
    },
    method: 'GET',
    path: '/',
    handler: function (request, reply) {

        reply(request.payload);
    }
});

풍모

hapi가 더 큰 기능 세트를 제공하는지 확인하려면 두 프로젝트의 API 문서 만 비교하면됩니다.

hapi에는 Express에서 제공하지 않는 다음과 같은 내장 기능이 포함되어 있습니다.

확장 성 및 모듈성

hapi와 Express는 상당히 다른 방식으로 확장 성을 추구합니다. Express를 사용하면 미들웨어 기능이 있습니다. 미들웨어 함수는 일종의 필터와 비슷하며 처리기를 치기 전에 모든 요청이 필터를 통과합니다.

hapi에는 요청 수명주기가 있으며 확장 점을 제공 하는데, 이는 미들웨어 기능과 비교할 수 있지만 요청 수명주기에 정의 된 여러 지점이 있습니다.

Walmart가 hapi를 구축하고 Express 사용을 중단 한 이유 중 하나는 Express 앱을 별도의 부분으로 나누고 다른 팀 구성원이 자신의 청크에 대해 안전하게 작업하는 것이 매우 어려웠 기 때문입니다. 이러한 이유로 그들은 hapi 에서 플러그인 시스템 을 만들었습니다 .

플러그인은 하위 애플리케이션과 같습니다. hapi 앱에서 할 수있는 모든 작업을 수행하고 경로, 확장 점 등을 추가 할 수 있습니다. 플러그인에서는 순서가 다르기 때문에 애플리케이션의 다른 부분을 중단하지 않을 수 있습니다. 경로 등록은 중요하지 않으며 충돌하는 경로를 만들 수 없습니다. 그런 다음이 플러그인을 서버에 결합하여 배치 할 수 있습니다.

생태계

Express는 기본 제공되는 기능을 거의 제공하지 않기 때문에 프로젝트에 추가해야 할 경우 외부를 살펴 봐야합니다. hapi로 작업 할 때 많은 경우에 필요한 기능은 내장되어 있거나 핵심 팀에서 만든 모듈이 있습니다.

최소한의 소리가 좋습니다. 그러나 심각한 프로덕션 앱을 구축하는 경우 결국에는이 모든 것들이 필요할 것입니다.

보안

hapi는 Walmart 팀이 Black Friday 트래픽을 실행하도록 설계되었으므로 보안과 안정성이 항상 가장 큰 관심사였습니다. 이러한 이유로 프레임 워크는 프로세스 페이로드 소진을 방지하기 위해 수신 페이로드 크기 제한과 같은 추가 작업을 많이 수행합니다. 또한 최대 이벤트 루프 지연, 사용 된 최대 RSS 메모리 및 v8 힙의 최대 크기와 같은 옵션이 있으며, 그 이상으로 서버가 충돌하지 않고 503 시간 초과로 응답합니다.

요약

그들 자신을 평가하십시오. 귀하의 요구와 두 가지 중 어떤 것이 가장 큰 관심사를 해결하는지 생각해보십시오. 두 커뮤니티 (IRC, Gitter, Github)에 관심이 있으시면 선호하는 사이트를 확인하십시오. 내 말만 듣지 마 그리고 행복한 해킹!


면책 조항 : hapi 에 관한 의 저자로서 편견이 있으며 위의 내용은 대부분 내 개인적인 의견입니다.


답변

우리 조직은 Hapi와 함께 가고 있습니다. 이것이 우리가 좋아하는 이유입니다.

하피는 :

  • 주요 군단의 지원을받습니다. 이는 커뮤니티 지원이 강력하고 향후 릴리스 전체에 걸쳐 귀하를위한 것임을 의미합니다. 열정적 인 Hapi 사용자를 쉽게 찾을 수 있으며, ExpressJs 자습서만큼 많지는 않지만 훌륭한 자습서가 있습니다. 이 게시일 현재 npm과 Walmart는 Hapi를 사용합니다.
  • API의 나머지 부분에 대한 포괄적 인 지식이 없어도 백엔드 서비스의 다양한 부분에서 작업하는 분산 팀의 작업을 용이하게 할 수 있습니다 (Hapi의 플러그인 아키텍처는 이러한 품질의 전형입니다).
  • 프레임 워크가 프레임 워크가 수행하는 작업을 수행하게하십시오. 그 후 프레임 워크는 보이지 않아야하며 개발자가 비즈니스 논리를 구축하는 데 실제 창의적 에너지에 집중할 수 있어야합니다. 1 년 동안 Hapi를 사용한 후에는 Hapi가이를 달성했다고 확신합니다. 난 행복 해요!

Eran Hammer (Hapi ‘s lead)의 의견을 직접 듣고 싶다면

지난 4 년 동안 hapi는 크고 작은 많은 프로젝트에서 선택의 틀로 성장했습니다. hapi를 독특하게 만드는 것은 대규모 배포 및 대규모 팀으로 확장 할 수 있다는 것입니다. 프로젝트가 성장함에 따라 엔지니어링 복잡성 및 프로세스 복잡성 등 복잡성이 증가합니다. hapi의 아키텍처와 철학은 지속적으로 코드를 리팩터링 할 필요없이 복잡성을 증가시킵니다. [더 읽기]

Hapi는 동일한 “스타 파워”를 갖지 않기 때문에 ExpressJs만큼 쉬운 방법이 아닙니다. 몇 년 동안 ExpressJ를 무책임하게 사용한 새로운 해커로 약 2 개월이 걸렸습니다. 숙련 된 백엔드 개발자라면 문서를 읽는 방법을 알게 될 것입니다. 아마 이것을 눈치 채지 못할 것입니다.

Hapi 문서가 개선 될 수있는 영역 :

  1. 사용자를 인증하고 세션을 만드는 방법
  2. CORS (Cross-Origin-Requests) 처리
  3. 파일 업로드 (멀티 파트, 청크 분할)

어떤 인증 전략 (기본 인증, 쿠키, JWT 토큰, OAuth)을 사용해야하는지 결정해야하기 때문에 인증이 가장 어려운 부분이라고 생각합니다. 세션 / 인증 환경이 너무 세분화되는 것은 기술적으로 Hapi의 문제는 아니지만 … 그러나 이것에 대해 약간의 손을 잡고 싶습니다. 개발자의 행복을 크게 높일 것입니다.

나머지 두 개는 실제로 그렇게 어렵지는 않지만 문서는 약간 더 잘 작성 될 수 있습니다.


답변

Hapi에 대한 간단한 사실 또는 왜 Hapi JS입니까?

Hapi는 구성 중심입니다. 인증 및 권한 부여 프레임 워크에 내장되어 있습니다. 테스트를 거친 환경에서 출시되었으며 실제로 그 가치가 입증되었습니다. 모든 모듈은 100 % 테스트 범위를가집니다. 핵심 HTTP에서 멀리 떨어진 최고 수준의 추상화를 쉽게 등록 할 수 있습니다. 플러그인 아키텍처를 통해

Hapi가 더 나은 선택 성능 현명한 Hapi는 다른 라우팅 메커니즘을 사용하여 더 빠른 조회를 수행하고 등록 순서를 고려할 수 있습니다. 그럼에도 불구하고 Express와 비교할 때 상당히 제한적입니다. 또한 Hapi 플러그인 시스템 덕분에 향후 여러 가지 방법으로 응용 프로그램에 도움이되는 다양한 패싯과 서비스를 분리 할 수 ​​있습니다.

용법

Hapi는 Express와 비교할 때 가장 선호되는 프레임 워크입니다. Hapi는 주로 대규모 엔터프라이즈 응용 프로그램에 사용됩니다.

엔터프라이즈 응용 프로그램을 만들 때 개발자가 Express를 선택하지 않는 몇 가지 이유는 다음과 같습니다.

Express에서 경로를 작성하기가 더 어렵습니다.

미들웨어는 대부분 방해가됩니다. 경로를 정의 할 때마다 많은 수의 코드를 작성해야합니다.

RESTful API를 구축하려는 개발자에게는 Hapi가 최선의 선택입니다. Hapi에는 마이크로 서비스 아키텍처가 있으며 특정 매개 변수를 기반으로 한 핸들러에서 다른 핸들러로 제어를 전송할 수도 있습니다. Hapi 플러그인을 사용하면 비즈니스 로직을 쉽게 관리 할 수있는 부분으로 나눌 수 있으므로 HTTP에 대한 더 높은 수준의 추상화를 즐길 수 있습니다.

Hapi의 또 다른 큰 장점은 잘못 구성 할 때 자세한 오류 메시지를 제공한다는 것입니다. Hapi에서는 기본적으로 파일 업로드 크기를 구성 할 수 있습니다. 최대 업로드 크기가 제한되어 있으면 파일 크기가 너무 크다는 오류 메시지를 사용자에게 보낼 수 있습니다. 파일 업로드가 더 이상 전체 파일을 버퍼링하지 않기 때문에 서버 충돌을 방지 할 수 있습니다.

  1. Express를 사용하여 달성 할 수있는 모든 내용은 hapi.js를 사용하여 쉽게 달성 할 수 있습니다.

  2. Hapi.js는 매우 세련되고 코드를 잘 구성합니다. 라우팅을 어떻게 수행하고 컨트롤러에 핵심 로직을 넣는다면 당신은 그것을 좋아할 것입니다.

  3. Hapi.js는 공식적으로 토큰 기반 인증에서 세션 관리에 이르기까지 hapi.js 전용 플러그인을 제공합니다. 그것은 전통적인 npm을 사용할 수 없다는 것을 의미하지는 않습니다. 모두 hapi.js가 지원합니다.

  4. hapi.js로 코드를 작성하면 코드를 유지 관리 할 수 ​​있습니다.


답변

최근에 Hapi를 사용하기 시작했으며 매우 만족합니다. 내 이유는

  1. 테스트하기가 더 쉽습니다. 예를 들면 다음과 같습니다.

    • server.inject 앱을 실행하고 실행하지 않고 응답을받을 수 있습니다.
    • server.info 현재 URI, 포트 등을 제공합니다.
    • server.settings구성에 액세스합니다. 예 : server.settings.cache현재 캐시 공급자를 가져 옵니다.
    • 의심스러운 /test경우 앱의 일부 또는 지원되는 플러그인 의 폴더를보고 조롱 / 테스트 / 스텁 등의 방법에 대한 제안을 확인하십시오.
    • 내 감각은 hapi의 아키텍처 모델을 통해 신뢰할 수는 있지만 예를 들어 내 플러그인이 등록되어 있습니까? 모듈 의존성을 어떻게 선언 할 수 있습니까?
  2. 파일 업로드 , 엔드 포인트에서 리턴 스트림 등과 같이 즉시 작동 합니다.

  3. 필수 플러그인은 핵심 라이브러리와 함께 유지 관리됩니다. 예를 들어 템플릿 파싱 , 캐싱 등 부가적인 이점은 동일한 코딩 표준이 필수 사항에 적용된다는 것입니다.

  4. Sane 오류 및 오류 처리 Hapi는 구성 옵션의 유효성을 검사 하고 내부 경로 테이블을 유지하여 중복 경로를 방지합니다. 디버깅이 필요한 예기치 않은 동작 대신 오류가 일찍 발생하기 때문에 학습하는 동안 매우 유용합니다.


답변

덧붙여 말하면, Hapi는 버전 16부터 ‘http2’호출을 지원하기 시작했습니다 (잘못되지 않은 경우). 그러나 express는 express 4까지 ‘http2’모듈을 직접 지원하지 않습니다. 비록 express 5의 알파 버전에서이 기능을 릴리스했지만.


답변

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


답변