◆ プレフィックス指定でまとめて設定する機能は hapi にはないみたい
◆ ルートごとに定義を作るって考えだから ルートのオブジェクト側を工夫する
  ◆ 動的にオブジェクトを書き換える

ルートベースの設定のデメリット

hapi はミドルウェアの考え方とは違って ルートごとに設定を書くタイプなので ミドルウェアでは楽にできてたことができなかったりします

ルーティング前の静的ファイルのサーブ

これまでも 「ルートの有無より先に公開フォルダ内の対応するパスにファイルがあれば先にそれを返してしまって 無い場合にだけ定義したルートごとの処理をしたい」 というときに困りました
hapi のグローバルミドルウェア

一応エクステンションという形でフレームワーク側の処理の合間に任意の処理をすることはできます
onPreHandler や onPreResponse などです

しかし 本来のルートの処理ではないので ここで静的ファイルを返そうとすると通常のルートでの処理より面倒になります
hapi でやるよりも Nginx とかを前段に置いて 静的ファイルの処理はそっちでやって Node.js 側で扱わないほうが楽に思えるくらいです
しかし 静的ファイルでも認証が必要で それを hapi で管理してる場合は前段でやるのは難しいです

ミドルウェアなら最初に認証が必要なエリアはチェックして 次に静的ファイルがあればそれを返して 最後にルーティングしてルートごとの処理とできるのですけどね

ルーティングのマウント

また ルートを /foo, /bar のようにいくつか定義して それらをグループとして /api の中にマウントするようなこともできないです
探してみると /api/{any*} のルートを作ってそこで一括して受け取って any を使って対応するモジュールをロードしてる例はありました
しかし それだと any の中にパラメータがあるとパースされませんし ルートごとの設定を全く活かせません

サブルート用に別の hapi インスタンスを作って inject したレスポンスを使うことも考えましたが それもあまり良い方法には思えません

他には プラグイン機能を使えば プラグインごとに realm が用意されてルートのプレフィックスを指定できるのでこれを利用することもできます

server.route({
method: "GET",
path: "/page",
handler: (request, h) => {
return "Hello World! at page"
},
})

server.route({
method: "GET",
path: "/",
handler: (request, h) => {
return "Hello World!"
},
})

const plugin = {
name: "prefix-route",
register(server, options) {
server.route({
method: "GET",
path: "/test",
handler(request, h) {
return "ok"
},
})
},
}

await server.register(plugin, { routes: { prefix: "/foo/bar" } })

for (const { method, path } of server.table()) {
console.log(method, path)
}
get /page
get /
get /foo/bar/test

/test のルートを追加するプラグインを用意して そのプラグインを register するときのオプションで prefix に /foo/bar を指定しています
これで 登録されたルートは /foo/bar/test になっています

実現はできるのですが hapi に機能を追加するためのプラグインを登録する仕組みなのに 通常のサーバのルートの定義に使うのはなんかおかしい気がしますし 手順的に面倒です

プレフィックス付きでルートを作れるプラグインということにすればいいような気もしますが こういう感じになります

const plugin = {
name: "sub-route",
register: function (server, options) {
server.route(options.sub_routes)
},
}

const sub_routes = [
{
method: "GET",
path: "/test",
handler: function (request, h) {
return "ok"
},
},
]

await server.register(
{
plugin,
options: {
sub_routes,
},
},
{ routes: { prefix: "/foo/bar" } }
)

プラグインの処理はオプションを server.route に入れるだけになってしまいます
また prefix はプラグインではなく register のオプションなので 設定が 2 箇所に分かれるのも扱いやすくないです

hapi らしく

そもそも hapi がミドルウェアを使わないのは ミドルウェアだと規模が大きくなってくると管理が辛くなってくるので ルートベースの設定ファイルの方が優れている という考え方だからです
作りやすさで言えばミドルウェアですが 作った人以外が見てどうなってるかを把握しやすいかというと ルートの設定を見るだけで済む hapi だとは思います

そういう方針なら仕方ないかなとは思うのですが 実際に作っていると不便に思うことが多いです
特に /private 以下のルートは全部に認証必要にしたい というときに全部に auth を書いていくのは流石に面倒です
設定漏れが出そうな気しかしません

server.route に渡すものはただのオブジェクトなので 渡す直前にまとめて自動で追加するようにしてみました
ルート定義を動的に作ってはいけないというわけでもないですし 完全に静的にするのは色々無理がありますからね
最終的に server.route に登録する設定オブジェクトを見れるので ルートごとの定義を見れるメリットは残っています

[routes/public.js]
export default () => [
{
method: "GET",
path: "/page1",
handler() {
return "a"
}
},
{
method: "GET",
path: "/js/{params*}",
handler: {
directory: {
path: ".",
redirectToSlash: true,
index: true,
}
}
},
...
]

[routes/private.js]
export default () => [
{
method: "GET",
path: "/page2",
handler() {
return "b"
},
},
{
method: "GET",
path: "/api/foo",
handler() {
return {}
}
},
...
]

[routes.js]
import public from "./routes/public.js"
import private from "./routes/private.js"

export default () => [
...public(),
...private().map(route => {
route.path = "/private" + route.path
route.options = route.options || {}
route.options.auth = route.options.auth || "simple"
return route
}),
]

[index.js]
import createRoutes from "./routes.js"

...

const routes = createRoutes()
console.debug(routes)
server.route(routes)

...

console.debug の出力を見ると

[
{ method: 'GET', path: '/page1', handler: [Function: handler] },
{
method: 'GET',
path: '/js/{params*}',
handler: { directory: [Object] }
},
...
{
method: 'GET',
path: '/private/page2',
handler: [Function: handler],
options: { auth: 'simple' }
},
{
method: 'GET',
path: '/private/api/foo',
handler: [Function: handler],
options: { auth: 'simple' }
},
...
]

routes/private.js が export するルートには /private プレフィックスと auth が追加されています

こういう感じで hapi にルートを登録する前にプログラム的に処理してしまうのが良いのかなと思います
ルート定義オブジェクトのフラットな配列なので 簡単に全体をループして正規表現で path をチェックしてオプションを追加などもできます
ルート定義前の 1 回のみでリクエスト時には影響しませんし ルート数が何万とかあることはまずないと思うので パフォーマンス的にも気にするほどじゃないですし