middleware icon indicating copy to clipboard operation
middleware copied to clipboard

defaultHook not picked up in nested routes

Open marceloverdijk opened this issue 1 year ago • 8 comments

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

marceloverdijk avatar Apr 17 '24 18:04 marceloverdijk

See https://github.com/honojs/middleware/issues/323

marceloverdijk avatar Apr 17 '24 20:04 marceloverdijk

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:

  • prisma var set
  • logger applied (as configured on the root app)
  • prettyJSON applied (as configured on the root ap)
  • defaultHook error handling not working

Now when using the createApp also to create the sub-routes app like const petsApp = createApp(); then:

  • prisma var set twice
  • logger applied twice
  • prettyJSONapplied
  • defaultHook applied

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.

marceloverdijk avatar Apr 18 '24 06:04 marceloverdijk

having the same issue

leognmotta avatar Jan 02 '25 13:01 leognmotta

@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.

yusukebe avatar Jan 06 '25 08:01 yusukebe

Np. I work d around it. I will have a look later this week and provide a sample.

marceloverdijk avatar Jan 06 '25 12:01 marceloverdijk

I'll transfer this issue to honojs/middleware.

yusukebe avatar Jul 14 '25 05:07 yusukebe

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??

bombillazo avatar Aug 07 '25 01:08 bombillazo

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.

bombillazo avatar Aug 07 '25 01:08 bombillazo