Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alternative OpenAPI middleware - thoughts on including it in this repo? #758

Open
paolostyle opened this issue Oct 1, 2024 · 2 comments

Comments

@paolostyle
Copy link
Contributor

Hey!

First of all, I'm not trying to promote my library. I'd like to include it in this repo as an official... not sure, either replacement or alternative to the existing zod-openapi middleware.

While I really love Hono and the ecosystem around it, I can't quite say the same about the OpenAPI middleware (zod-openapi). Perhaps I'm alone in this but I developed my apps with just zod-validator (which works great) and I was quite surprised that creating an OpenAPI documentation would require me to refactor pretty much the entire Hono code - you no longer can use the standard Hono class, .get/.post etc. methods (well I guess you can but they wouldn't be documented), I would have to use z object from the library, even gradual migration is difficult. I did not like that and I ended up not implementing OpenAPI in my app at all because of that.

Instead I spent some time to try and create a middleware that would be much simpler, something that would work similarly to zod-validator middleware and would be easy to gradually implement. I believe I succeeded in that and although it's a bit rough around the edges at the moment and I still need to test some edge cases, I do have my apps documented with that library and it works quite well.

A very simple example of how my middleware works:

import { Hono } from 'hono';
import { z } from 'zod';
import { createOpenApi, openApi } from 'hono-zod-openapi';

export const app = new Hono().get(
  '/user',
  openApi(
    // simple response type
    z.object({ hi: z.string() }),
   // request validators, uses zod-validator internally
    {
      query: z.object({ id: z.string() }),
    },
  ),
  (c) => {
    // this still works exactly as with `zod-validator`
    const { id } = c.req.valid('query');
    return c.json({ hi: id }, 200);
  },
)

// this will add a `GET /doc` route to the `app` router
// it's possible to do it manually, also possible to manually adjust the OpenAPI document
createOpenApi(app, {
  title: 'Example API',
  version: '1.0.0',
});

You can pass the response type also as a more complex object or array of them:

[
  {
    status: 200,
    description: '200 OK',
    schema: z
      .object({
        message: z.string(),
      })
      .openapi({
        examples: [
          {
            message: 'Hello, World!',
          },
        ],
      }),
  },
]

There is also support for validating the response but I'm on the fence whether it's actually useful. I think it would be more useful if c.json in the handler would be able to pick up that type and scream at you if it didn't match the schema.

OpenAPI functionality is handled by zod-openapi (so not the same library the official middleware is using, I found this one to be nicer to work with).

You can check out the repo here: https://github.com/paolostyle/hono-zod-openapi.

Now the main question - is there a place for a library like this here, in this repo, considering an official one already exists? Or should I just keep maintaining it in my repo? If it would be possible to include, how should I go about it? Just create a PR and we'd talk about the implementation details etc. there?

@bennobuilder
Copy link

@paolostyle I appreciate the approach you're taking with zod-openapi, especially the middleware pattern instead of wrapping routes.

That said, I'd prefer a more explicit way to define the OpenAPI schema, similar to zod-openapi, without excessive abstraction. A potential solution could be allowing the middleware to take a specific zod-openapi object for each route, while offering a helper function for an easier migration path. For example:

export const app = new Hono().get(
  '/user',
  openApi(
    fromValidators([zValidator('json', JsonSchema), zValidator('query', QuerySchema)])
  ),
  (c) => {
    const { id } = c.req.valid('query');
    return c.json({ hi: id }, 200);
  },
);

This would streamline migration from zod-validator, by simply wrapping validators with openApi(fromValidators(...)), while also providing a more detailed API for those of us who prefer less abstraction in OpenAPI schema definitions.

This flexibility would definitely make me consider switching to your library over defining OpenAPI schemas first and using my openapi-router for typesafe Hono routes based on the OpenAPI spec.

cheers :)

@paolostyle
Copy link
Contributor Author

@bennobuilder Thanks for the comment. I'm not sure if I'm following, though. So just to be clear, the second argument of my middleware defined like this: { json: someZodSchema, header: otherZodSchema } is equivalent to passing two zodValidator middlewares like this: zValidator('json', someZodSchema), zValidator('header', otherZodSchema). The added value of the middleware is that you get the OpenAPI spec for free, OpenAPI specific options are handled through .openapi method on the zod schema, just like in the current version.

For response schemas it is indeed slightly more abstracted but at the end of the day I wanted to do it mostly for myself and I hate the verbosity of the spec, I'd much rather pass a flat list of possible responses than defining a 6 levels deep json/yaml structure. I understand that the spec is somewhat of an industry standard, but my goal was to get an accurate Swagger docs page without needing to deal with yamls. The goal of my middleware is that the developer should spend as little time as possible on the OpenAPI quirks and get reasonable quality docs almost for free while writing idiomatic Hono code. createOpenApi function (the one that actually creates the document) accepts an overrides method where you can modify the doc however you want, and it receives a generated paths object which can be modified as required. This is obviously not the recommended way, I haven't figured out yet if there are things that are impossible to do in the middleware API, things like servers definitely need to be specified there but it's not really something that can be reliably inferred from the Hono object.

If my library was to replace the current one I would definitely have to support the existing createRoute API at least as a migration path, if that's what you meant by "specific zod-openapi object".

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

No branches or pull requests

2 participants