defaultHook not picked up in nested routes
What version of Hono are you using?
4.2.4
What runtime/platform is your app running on?
Cloudflare Pages Functions
What steps can reproduce the bug?
Create a base app like below with a default hook handling validation errors:
export const createApp = () => {
const app = new OpenAPIHono<Env>({
defaultHook: (result, c) => {
if (!result.success) {
return c.json(
{
ok: false,
errors: formatZodErrors(result),
source: 'custom_error_handler',
},
422,
);
}
},
});
app.use(logger());
app.use(prettyJSON());
app.use(async (c, next) => {
const adapter = new PrismaD1(c.env.DB);
const prisma = new PrismaClient({ adapter });
c.set('prisma', prisma);
await next();
});
return app;
}
Routes then directly connected to the app instance like will correctly use this defaultHook as expected, e.g.:
app.openapi(
createRoute({
method: 'get',
path: '/api/pets/{id}',
..
but when nesting routes like:
app.route('/books', books);
the book routes will not use the defautHook as defined on the base app.
What is the expected behavior?
That the books routes will also use the defaultHook from the base app for handling validation errors.
What do you see instead?
No response
Additional information
No response
See https://github.com/honojs/middleware/issues/323
I'm reopening this issue becasue:
I have a create-app with:
export const createApp = () => {
const app = new OpenAPIHono<Env>({
defaultHook: (result, c) => {
if (!result.success) {
return c.json(
{
ok: false,
errors: formatZodErrors(result),
source: 'custom_error_handler',
},
422,
);
}
},
});
app.use(logger());
app.use(prettyJSON());
app.use(async (c, next) => {
console.log('setting prisma');
const adapter = new PrismaD1(c.env.DB);
const prisma = new PrismaClient({ adapter });
c.set('prisma', prisma);
await next();
});
return app;
};
it sets a defaultHook for handling validation errors but also applies logger, prettyJSON and a custom middleware that sets the Prisma client.
I created my root app like:
const app = createApp();
app.route('/api/pets', pets);
app.route('/api/events', events);
... more sub-routes
export const onRequest: PagesFunction<Env> = async (context) => {
return app.fetch(context.request, context.env, context);
};
The sub-routes I was initially creating like:
const petsApp = new OpenAPIHono<Env>();
app.openapi(
createRoute({
method: 'get',
path: '/',
responses: {
200: {
description: 'OK',
content: {
'application/json': {
schema: z.array(PetSchema),
},
},
},
},
}),
async (c) => {
const pets = await c.var.prisma.pet.findMany({
orderBy: { name: 'asc' },
});
const result: PetModel[] = petModelAssembler.toCollectionModel(pets);
return c.json(result);
},
);
export default petsApp;
Note that I'm using new OpenAPIHono<Env>() here, the createApp is only used for createing the root app and setting the defaults.
What this setup means for the pets sub-routes:
-
prismavar set -
loggerapplied (as configured on the root app) -
prettyJSONapplied (as configured on the root ap) -
defaultHookerror handling not working
Now when using the createApp also to create the sub-routes app like const petsApp = createApp(); then:
-
prismavar set twice -
loggerapplied twice -
prettyJSONapplied -
defaultHookapplied
E.g. from logs for a single request:
<-- GET /api/pets/29
setting prisma
<-- GET /api/pets/29
setting prisma
--> GET /api/pets/29 404 79ms
--> GET /api/pets/29 404 82ms
of course I could easily update my create-app.ts (which I did) to have createRootApp and createApp functions, but in general it feels inconsistent that middleware on the root app is propgated to child apps (using app.route(..)), but the defaultHook option is not.
having the same issue
@marceloverdijk
Sorry for the super late reply. defaultHook is a Zod OpenAPI Hono's feature so this issue should not be here honojs/hono. But I can investigate it if you share a minimal project to reproduce it.
Np. I work d around it. I will have a look later this week and provide a sample.
I'll transfer this issue to honojs/middleware.
Hey, we just hit this issue, we can't composably set the defaultHook to our main app and then attach other routers, we now have to put the defaultHook to each subrouter for it to work.
A bit related, in reality, we want to handle the validation errors in our Global error handler (app.onError) instead. How can we bypass this defaultHook and pass the error on to the Global error handler??
Glad I found this thread and I was able to get the expected behavior by throwing inside the defaultHook.
Ideally, we could have the "main" defaultHook function propagate to subrouters instead of passing it to every individual router.
This would probably require an additional option like "globalHook" : true for this behavior since that would be a breaking change.