Skip to content

Conversation

@kevin-dp
Copy link

We're using zod-openapi and found that route middleware runs before the validators. As a result, we don't have access to validated input (like c.req.valid('param')) inside route middleware, which was somewhat surprising to us.

I can imagine special use cases where one might want route middleware to run first (e.g. when dynamically adding parameters) but i would imagine that to be less common. If we really want we could make this configurable.

@changeset-bot
Copy link

changeset-bot bot commented Apr 22, 2025

🦋 Changeset detected

Latest commit: ea95a7d

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@codecov-commenter
Copy link

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 79.53%. Comparing base (abb2606) to head (ea95a7d).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1133   +/-   ##
=======================================
  Coverage   79.53%   79.53%           
=======================================
  Files          77       77           
  Lines        2282     2282           
  Branches      578      578           
=======================================
  Hits         1815     1815           
  Misses        391      391           
  Partials       76       76           
Flag Coverage Δ
zod-openapi 5.17% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@yusukebe
Copy link
Member

@kevin-dp

You can access it with the code like the following:

import { z, createRoute, OpenAPIHono } from '@hono/zod-openapi'

const createUserRoute = createRoute({
  method: 'post',
  path: '/user',
  request: {
    body: {
      content: {
        'application/json': {
          schema: z.object({
            name: z.string(),
          }),
        },
      },
    },
  },
  middleware: [
    async (c, next) => {
      await next()
      // @ts-expect-error not typed well
      console.log(c.req.valid('json')) // You can get the validated value
    },
  ] as const,
  responses: {
    200: {
      description: 'foo',
    },
  },
})

const app = new OpenAPIHono().openapi(createUserRoute, async (c) => {
  return c.json('foo')
})

export default app

@kevin-dp
Copy link
Author

Hi @yusukebe,

Our middleware is authenticating the request. Calling next() at the beginning of our middleware would allow us to access the validated inputs inside the middleware but would mean that the route handler is being executed before we actually authenticate the request.

@yusukebe
Copy link
Member

@kevin-dp

Is that possible with your PR? I don't know what code you want to implement but you may be able it with the hook:

https://github.com/honojs/middleware/tree/main/packages/zod-openapi#handling-validation-errors

If you want to get more detailed help, please provide a concrete and minimal code.

@kevin-dp
Copy link
Author

kevin-dp commented Apr 24, 2025

Sure, here's a small snippet that exemplifies what we're trying to achieve @yusukebe

const userBelongsToTeam = async (c, next) => {
  const email = c.get(`email`)!
  const { team_id } = c.req.valid(`param`) // This fails because c.req.valid(`param`) is undefined

  // check in the database if the user (identified by email) belongs to the team
  const userBelongsToTeam = ...

  if (!userBelongsToTeam) {
    throw new HTTPException(403, { message: "You don't have access to this team" })
  }

  return next()
}

app.openapi(
  createRoute({
    method: `get`,
    path: `/team/{team_id}`,
    middleware: [userBelongsToTeam] as const,
    ...
  }),
  async (c) => {
    // Should only reach here if the user belongs to the team
  }
)

@yusukebe
Copy link
Member

@kevin-dp

Thank you for the explanation. I got it. But this will introduce a breaking change, although the test is not failing. And this is not a fix issue, but a new feature. We can't accept this PR as is now.

@kevin-dp
Copy link
Author

@yusukebe sure, i understand that. What is the reason for running the validators after the middleware? I was very surprised by that behavior.

@yusukebe
Copy link
Member

@kevin-dp

You may be surprised, but there are many people who are happy with the current specifications. For example, cases where they want to set values with c.set() before validation.

@yusukebe
Copy link
Member

yusukebe commented Apr 28, 2025

Plus, we have historical reasons. This Zod OpenAPI is based on Zod Validator or other validators. The old version of hono can't infer types in a handler when there is middleware after the validator.

The following is using the old version of hono:
CleanShot 2025-04-28 at 17 19 40@2x

So, reference implementations use middleware before the validator. So, this Zod OpenAPI is following it, though, as you said, in some cases it is convenient if we put middleware after validators.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants