Closed3

Hono の Route Handler 関数の定義時に複数の Middleware の型を扱えるようにするヘルパー関数を実装する

uttkuttk

やりたいこと

Route Handler 関数と Route 適用を別ファイルで行いたい場合がある 👇

./api.ts
// Route Handler 関数を実装
export const apiRouteHandler = (ctx) => {
  return ctx.json({ message: "宇宙ネコは常にあなたのすぐ近くに居ます🐈" })
}
./index.ts
import { Hono } from "hono";
import { apiRouteHandler } from "./api.ts";

const post = 3000;
const app = new Hono();

// ここで `/api` に apiRouteHandler を設定する
app.get("/api", apiRouteHandler);

console.log(`Server is running on ${port}`);

serve({
  port,
  fetch: app.fetch,
});

通常の開発ではルートが多くなるので、上記のように実装と Route の設定を別ファイルで管理できると開発しやすくなって嬉しい。

ただし、TypeScript を使っている場合は型が効かなくなるのであまり良くないし、公式でも推奨していない 👇

https://hono.dev/guides/best-practices

なので、公式が用意してくれている createFactory() を使って実装するのが良い 👇

./api.ts
import { createFactory } from 'hono/factory'

interface ApiEnv {
  Variables: {
    message: string;
  }
}

// ココで明示的に型を渡す必要がある
const factory = createFactory<ApiEnv>()

// middleware を定義
const middleware = factory.createMiddleware((ctx, next) => {
  ctx.set("message", "宇宙ネコは常にあなたのすぐ近くに居ます🐈")
  return next();
})

// Factory を使って Route Handler を実装する
export const apiRouteHandler = factory.createHandler(
  middleware,
  (ctx) => {
    const message = ctx.get("message"); // ApiEnv を型引数に渡したことによって型が付いている
    return ctx.json({ message })
  }
);

が、明示的に型を指定しないとまともに型が付かなかったり、複数の Middleware が絡む場合は型の定義が難しいなどの問題がある 👇

./api.ts
+ import { hogeMiddleware } from "./middlewares.ts"
  import { createFactory } from 'hono/factory'

  interface ApiEnv {
    Variables: {
      message: string;
    }
  }

+ // ココで明示的に型を渡すのが面倒
+ const factory = createFactory<ApiEnv>()

 // middleware を定義
 const middleware = factory.createMiddleware((ctx, next) => {
   ctx.set("message", "宇宙ネコは常にあなたのすぐ近くに居ます🐈")
   return next();
 })

 // Factory を使って Route Handler を実装する
 export const apiRouteHandler = factory.createHandler(
   middleware
+  hogeMiddleware, // ApiEnv と違う型を使っている場合は型エラーが発生し使えない
   (ctx) => {
     const message = ctx.get("message"); // ApiEnv を型引数に渡したことによって型が付いている
     return ctx.json({ message })
   }
 );

なので、型を明示的に渡さなくても良くて、複数の Middleware を使っても型がちゃんとつくように Route Handler を定義するためのヘルパー関数を実装する。

uttkuttk

ヘルパー関数を実装する

./helper.ts
import type {
  Env,
  Next,
  Context,
  BlankInput,
  HandlerResponse,
  MiddlewareHandler,
} from "hono";

export type RouteHandler<E extends Env> = (
  c: Context<E, string, BlankInput>, next: Next
) => HandlerResponse<any>;

type GetEnv<
  M extends readonly MiddlewareHandler[],
  R = NonNullable<unknown>,
> = M extends [infer T, ...infer A]
  ? T extends MiddlewareHandler<infer V>
    ? A extends MiddlewareHandler[]
      ? A extends { length: 0 }
        ? { Variables: V["Variables"] & R }
        : GetEnv<A, V["Variables"]>
      : { Variables: V["Variables"] & R }
    : { Variables: R }
  : Env;

export const createRouteHandler = <M extends readonly MiddlewareHandler[]>({
  handler,
  middlewares = [] as unknown as [...M],
}: {
  handler: RouteHandler<GetEnv<[...M]>>;
  middlewares?: [...M];
}): [...M, RouteHandler<GetEnv<[...M]>>] => {
  return [...middlewares, handler];
};
uttkuttk

使い方

上記の createRouteHandler() の使い方は以下のようになる 👇

./api.ts
import { hogeMiddleware, messageMiddleware } from "./middlewares.ts";
import { createRouteHandler } from "./helper.ts";

export const apiRouteHandlers = createRouteHandler({
  middlewares: [hogeMiddleware, messageMiddleware],
  handler: (ctx) => {
    const message = ctx.get("message"); // messageMiddleware によって型が付く
    const hoge = ctx.get("hoge"); // hogeMiddleware によって型が付く
    return ctx.json({ message, hoge });
  } 
})
./index.ts
import { Hono } from "hono";
import { apiRouteHandlers } from "./api.ts";

const post = 3000;
const app = new Hono();

// ここで `/api` に apiRouteHandlers を設定する
// apiRouteHandlers は配列であることに注意!
app.get("/api", ...apiRouteHandlers);

console.log(`Server is running on ${port}`);

serve({
  port,
  fetch: app.fetch,
});
このスクラップは2024/03/18にクローズされました