Closed3
Hono の Route Handler 関数の定義時に複数の Middleware の型を扱えるようにするヘルパー関数を実装する
やりたいこと
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 を使っている場合は型が効かなくなるのであまり良くないし、公式でも推奨していない 👇
なので、公式が用意してくれている 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 を定義するためのヘルパー関数を実装する。
ヘルパー関数を実装する
./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];
};
使い方
上記の 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にクローズされました