Hapiネストルーティング 質問する

Hapiネストルーティング 質問する

おおよそ次のような REST エンドポイントが必要だとします。

/projects/
/projects/project_id 

/projects/project_id/items/
/projects/project_id/items/item_id

それぞれに CRUD を実行するのは理にかなっています。たとえば、/projects POST は新しいプロジェクトを作成し、GET はすべてのプロジェクトを取得します。/projects/project_id GET は、そのプロジェクトだけを取得します。

アイテムはプロジェクト固有なので、特定のプロジェクトである project_id の下に配置します。

このようなネストされたルートを作成する方法はありますか?

今はこんな感じです:

  server.route({
    method: 'GET',
    path: '/projects',
    handler: getAllProjects
  });

  server.route({
    method: 'GET',
    path: '/projects/{project_id}',
    handler: getOneProject
  });

  server.route({
    method: 'GET',
    path: '/projects/{project_id}/items/{item_id}',
    handler: getOneItemForProject
  });

  server.route({
    method: 'GET',
    path: '/projects/{project_id}/items',
    handler: getAllItemsForProject
  })

しかし、私はアイテム ルートをプロジェクト ルートにネストし、プロジェクトをさらに渡す機能を実現する方法を探しています。

何かおすすめはありますか?

ベストアンサー1

hapi 自体には (私が知る限り)「サブルーティング」という概念はありませんが、基本は簡単に実装できます。

まず、ハピはワイルドカードパス内の変数を使用すると、基本的に特定のパスの包括的なルートを作成できます。例:

server.route({
  method: 'GET',
  path: '/projects/{project*}',
  handler: (request, reply) => {
    reply('in /projects, re-dispatch ' + request.params.project);
  }
});

これらのワイルドカード パスにはいくつかのルールがありますが、最も重要なのは、ワイルドカード パスは最後のセグメントにのみ存在できるということです。これは、ワイルドカード パスを「包括的なパス」と考えると納得できます。

上記の例では、{project*}パラメータは として使用可能でありrequest.params.project、呼び出されたパスの残りの部分が含まれます。たとえば、は にGET /projects/some/awesome/thing設定されます。request.params.projectsome/awesome/project

次のステップは、この「サブパス」(実際の質問)を処理することです。これは主に好みと作業方法の問題です。あなたの質問は、ほとんど同じものの無限の繰り返しリストを作成したくないが、同時に非常に具体的なプロジェクト ルートを設定できるようにしたいと考えているようです。

1 つの方法は、パラメータをチャンクに分割し、一致する名前を持つフォルダーを探すことですrequest.params.project。このフォルダーには、リクエストをさらに処理するためのロジックが含まれている可能性があります。

index.js特定のルートのハンドラーを簡単に含めることができるフォルダー構造 (ルートを含むファイルに対する相対構造、例) を想定して、この概念を検討してみましょう。

const fs = require('fs'); // require the built-in fs (filesystem) module

server.route({
    method: 'GET',
    path: '/projects/{project*}',
    handler: (request, reply) => {
        const segment = 'project' in request.params ? request.params.project.split('/') : [];
        const name = segment.length ? segment.shift() : null;

        if (!name) {
            //  given the samples in the question, this should provide a list of all projects,
            //  which would be easily be done with fs.readdir or glob.
            return reply('getAllProjects');
        }

        let projectHandler = [__dirname, 'projects', name, 'index.js'].join('/');

        fs.stat(projectHandler, (error, stat) => {
            if (error) {
                return reply('Not found').code(404);
            }

            if (!stat.isFile()) {
                return reply(projectHandler + ' is not a file..').code(500);
            }

            const module = require(projectHandler);

             module(segment, request, reply);
        });
    }
});

このようなメカニズムにより、各プロジェクトをアプリケーション内のノード モジュールとして保持し、実行時にパスを処理するために使用する適切なモジュールをコードで判断できるようになります。

method: ['GET', 'POST', 'PUT', 'DELETE']の代わりにを使用することで、ルートで複数のメソッドを処理できるため、すべてのリクエスト メソッドに対してこれを指定する必要さえありませんmethod: 'GET'

ただし、ルートの処理方法の繰り返し宣言は完全には処理されません。すべてのプロジェクトでかなり類似したモジュール設定が必要になるためです。

上記の例で「サブルート ハンドラ」を組み込んで呼び出す方法では、サンプルの実装は次のようになります。

//  <app>/projects/<projectname>/index.js
module.exports = (segments, request, reply) => {
    //  segments contains the remainder of the called project path
    //  e.g. /projects/some/awesome/project
    //       would become ['some', 'awesome', 'project'] inside the hapi route itself
    //       which in turn removes the first part (the project: 'some'), which is were we are now
    //       <app>/projects/some/index.js
    //       leaving the remainder to be ['awesome', 'project']
    //  request and reply are the very same ones the hapi route has received

    const action = segments.length ? segments.shift() : null;
    const item   = segments.length ? segments.shift() : null;

    //  if an action was specified, handle it.
    if (action) {
        //  if an item was specified, handle it.
        if (item) {
            return reply('getOneItemForProject:' + item);
        }

        //  if action is 'items', the reply will become: getAllItemsForProject
        //  given the example, the reply becomes: getAllAwesomeForProject
        return reply('getAll' + action[0].toUpperCase() + action.substring(1) + 'ForProject');
    }

    //  no specific action, so reply with the entire project
    reply('getOneProject');
};

これは、実行時にアプリケーション内で個々のプロジェクトを処理する方法を示していると思いますが、アプリケーション アーキテクチャを構築するときに対処する必要があるいくつかの懸念事項が発生します。

  • プロジェクト処理モジュールが非常に似ている場合は、同じモジュールを何度もコピーするのを防ぐために使用するライブラリを作成する必要があります。これにより、メンテナンスが容易になります (サブルーティングの最終的な目的はこれだったと思います)。
  • 実行時にどのモジュールを使用するかがわかれば、サーバー プロセスの開始時にもこれを判断できるはずです。

コードの繰り返しを防ぐためのライブラリを作成することは、メンテナンスが容易になり、将来の自分自身に感謝することになるため、早い段階で行う(行う方法を学ぶ)必要があります。

アプリケーションの開始時に、さまざまなプロジェクトを処理するためにどのモジュールが利用可能かを把握しておくと、すべてのリクエストで同じロジックを何度も適用する必要がなくなります。Hapi はこれをキャッシュできる場合があり、その場合は特に問題にはなりませんが、キャッシュがオプションでない場合は、あまり動的でないパスを使用する方がよい場合があります (これが、これが hapi でデフォルトで提供されない主な理由だと思います)。

<project>/index.jsアプリケーションの開始時にプロジェクトフォルダを走査してすべてを検索し、より具体的なルートを登録することができます。globこのような:

const glob = require('glob');

glob('projects/*', (error, projects) => {
    projects.forEach((project) => {
        const name = project.replace('projects/', '');
        const module = require(project);

        server.route({
            method: 'GET',
            path: '/projects/' + name + '/{remainder*}',
            handler: (request, reply) => {
                const segment = 'remainder' in request.params ? request.params.remainder.split('/') : [];

                module(segment, request, reply);
            }
        });
    });
});

これは、すべてのリクエストでモジュールを検索するという上記のロジックを効果的に置き換え、提供するすべてのプロジェクト モジュールに実際の処理を任せながら、どのプロジェクトを提供するかを正確に把握できるため、(わずかに) より効率的なルーティングに切り替えます。(ルートを実装することを忘れないでください/projects。これは明示的に行う必要があるためです)

おすすめ記事