middleware icon indicating copy to clipboard operation
middleware copied to clipboard

Swagger middleware

Open vincent-vade opened this issue 2 years ago • 14 comments

Hi,

Hono is awesome but is it possible to add swagger middleware ?

Best regards

Vincent

vincent-vade avatar Jun 18 '23 08:06 vincent-vade

Hi @vincent-vade!

Yes, creating a middleware for Swagger sounds like a good idea. Ideally, we would create an OpenAPI middleware, but we're still in the planning phase since we haven't started the API design yet. However, if you're talking about implementing just the Swagger UI, it might be simpler to accomplish.

If you're referring to the full suite of OpenAPI tools, we would need to start designing the middleware. For example, we would need to decide how to define schemas, whether it be with YAML, JSON, or a JS code base.

yusukebe avatar Jun 19 '23 07:06 yusukebe

Hope we can have an auto openapi middleware like Hapi.js Hapi generates openapi doc by Joi validator. Hono already has zod validator middleware. https://github.com/asteasolutions/zod-to-openapi

kiancyc avatar Jul 26 '23 09:07 kiancyc

"zod-to-openapi" looks good. I'll check it in more detail later.

yusukebe avatar Jul 27 '23 06:07 yusukebe

I think it'd be a good idea to also allow support for different validation libraries, so a modular design would be good. Maybe a general combination with the validation middlewares to just extract the schema from them and a mapper for validation lib to openapi schema

ZerNico avatar Jul 31 '23 19:07 ZerNico

@kiancyc

Is Hapi's generator this?: https://github.com/hapi-swagger/hapi-swagger

@ZerNico

Yeah, it's great that we can support Zod, Valibot, Joi, or others. Hono's validator (though the design might not be the best) can support any validator. So, we have Zod, Valibot, and TypeBox validators. If we implement something like that, we can handle any validator, and it can generate OpenAPI docs.

yusukebe avatar Aug 01 '23 09:08 yusukebe

I've tried "zod-to-openapi" and a new feature for the validator.

Even though it's not released yet, I've made the zod validator capable of receiving the schema argument to validate response types (just types) as the third argument:

const apiRoutes = api.get('/users/:id', zValidator('param', ParamsSchema, UserSchema), (c) => {
  const { id } = c.req.valid('param');
  //
  return c.jsonT({
    id: '123',
    name: 'foo',
    age: 30,
  });
})

UserSchema will validate response types, which are rewritten in jsonT(). Perhaps it should validate actual values, but for now, it validates types only.

Next, I created the schema using zod-to-openapi:

import type { RouteConfig } from '@asteasolutions/zod-to-openapi'
import { extendZodWithOpenApi, OpenAPIRegistry } from '@asteasolutions/zod-to-openapi'
import { z } from 'zod'

extendZodWithOpenApi(z)

const registry = new OpenAPIRegistry()

const ParamsSchema = z.object({
  id: registry.registerParameter(
    'UserId',
    z.string().openapi({
      param: {
        name: 'id',
        in: 'path',
      },
      example: '1212121',
    })
  ),
})

const UserSchema = z
  .object({
    id: z.string().openapi({
      example: '1212121',
    }),
    name: z.string().openapi({
      example: 'John Doe',
    }),
    age: z.number().openapi({
      example: 42,
    }),
  })
  .openapi('User')

const route = {
  method: 'get',
  path: '/users/:id',
  description: 'Get user data by its id',
  summary: 'Get a single user',
  request: {
    params: ParamsSchema,
  },
  responses: {
    200: {
      description: 'Object with user data.',
      content: {
        'application/json': {
          schema: UserSchema,
        },
      },
    },
  },
} as RouteConfig


registry.registerPath(route)

export { ParamsSchema, UserSchema, registry }

Then, it can return the docs as JSON from an endpoint:

app.get('/schema', prettyJSON(), (c) => {
  const generator = new OpenApiGeneratorV3(registry.definitions)
  const document = generator.generateDocument({
    openapi: '3.0.0',
    info: {
      version: '1.0.0',
      title: 'My API',
    },
    servers: [{ url: 'v1' }],
  })
  return c.json(document)
})

Result:

SS

Of course, you can generate it as a YAML file as well.

This looks good, and we can use it in the way described above already. However, we can make it more like an application.

yusukebe avatar Aug 01 '23 10:08 yusukebe

@kiancyc

Is Hapi's generator this?: https://github.com/hapi-swagger/hapi-swagger

@ZerNico

Yeah, it's great that we can support Zod, Valibot, Joi, or others. Hono's validator (though the design might not be the best) can support any validator. So, we have Zod, Valibot, and TypeBox validators. If we implement something like that, we can handle any validator, and it can generate OpenAPI docs.

Yes, it is.

kiancyc avatar Aug 01 '23 11:08 kiancyc

I can give two more web framework examples that has openai doc function buil-in:

Nest.JS TS FastApi Python

They all have openapi integration out of box.

kiancyc avatar Aug 01 '23 11:08 kiancyc

I've tried "zod-to-openapi" and a new feature for the validator.

Even though it's not released yet, I've made the zod validator capable of receiving the schema argument to validate response types (just types) as the third argument:

const apiRoutes = api.get('/users/:id', zValidator('param', ParamsSchema, UserSchema), (c) => {
  const { id } = c.req.valid('param');
  //
  return c.jsonT({
    id: '123',
    name: 'foo',
    age: 30,
  });
})

UserSchema will validate response types, which are rewritten in jsonT(). Perhaps it should validate actual values, but for now, it validates types only.

Next, I created the schema using zod-to-openapi:

import type { RouteConfig } from '@asteasolutions/zod-to-openapi'
import { extendZodWithOpenApi, OpenAPIRegistry } from '@asteasolutions/zod-to-openapi'
import { z } from 'zod'

extendZodWithOpenApi(z)

const registry = new OpenAPIRegistry()

const ParamsSchema = z.object({
  id: registry.registerParameter(
    'UserId',
    z.string().openapi({
      param: {
        name: 'id',
        in: 'path',
      },
      example: '1212121',
    })
  ),
})

const UserSchema = z
  .object({
    id: z.string().openapi({
      example: '1212121',
    }),
    name: z.string().openapi({
      example: 'John Doe',
    }),
    age: z.number().openapi({
      example: 42,
    }),
  })
  .openapi('User')

const route = {
  method: 'get',
  path: '/users/:id',
  description: 'Get user data by its id',
  summary: 'Get a single user',
  request: {
    params: ParamsSchema,
  },
  responses: {
    200: {
      description: 'Object with user data.',
      content: {
        'application/json': {
          schema: UserSchema,
        },
      },
    },
  },
} as RouteConfig


registry.registerPath(route)

export { ParamsSchema, UserSchema, registry }

Then, it can return the docs as JSON from an endpoint:

app.get('/schema', prettyJSON(), (c) => {
  const generator = new OpenApiGeneratorV3(registry.definitions)
  const document = generator.generateDocument({
    openapi: '3.0.0',
    info: {
      version: '1.0.0',
      title: 'My API',
    },
    servers: [{ url: 'v1' }],
  })
  return c.json(document)
})

Result:

SS Of course, you can generate it as a YAML file as well.

This looks good, and we can use it in the way described above already. However, we can make it more like an application.

I think the tricky part is to auto generate the RouteConfig.

kiancyc avatar Aug 01 '23 11:08 kiancyc

Express.js joi and swagger integration middleware:

https://github.com/vforv/express-joi-simple

import * as express from 'express';
import * as joi from 'joi';
import * as BodyParser from 'body-parser';
import { Doc, Validate, RequestHandler } from 'express-joi-simple';

const app = express();
app.use(BodyParser.json());

const schema = {
    body: {
        test1: joi.string().required()
    },
    model: 'Register'
}

// Note middleware here
app.post('register', Validate(schema), (req: any, res: any) => {
    res.json({
        message: 'register'
    })
})

app.use(RequestHandler);

app.listen(3000, () => {
    // Note function Doc here
    Doc(app);
})

kiancyc avatar Aug 01 '23 11:08 kiancyc

Express.js joi and swagger integration middleware:

https://github.com/vforv/express-joi-simple

import * as express from 'express';
import * as joi from 'joi';
import * as BodyParser from 'body-parser';
import { Doc, Validate, RequestHandler } from 'express-joi-simple';

const app = express();
app.use(BodyParser.json());

const schema = {
    body: {
        test1: joi.string().required()
    },
    model: 'Register'
}

// Note middleware here
app.post('register', Validate(schema), (req: any, res: any) => {
    res.json({
        message: 'register'
    })
})

app.use(RequestHandler);

app.listen(3000, () => {
    // Note function Doc here
    Doc(app);
})

This extracts the schema by calling the route with "schemaBypass" as the req parameter. The joi middlware then just returns the schema instead of doing validation. I'm not sure if that is the best way to handle something like that, it stops working if there is any other middleware infront of it that returns something or throws. But I also can't think of a good way to access the schema. Maybe attaching it to the returned Middleware function kind of like how displayName and such is set in react?

Also instead of modifying (or wrapping) the validation library zod-to-openapi does it'd probably be better to have another parameter to set the example object and just check the type of that with the passed schema. Would make it easier to use with different libs.

This goes very deep into Hono then, would a plugin API make more sense to accomplish something like this?

ZerNico avatar Aug 01 '23 14:08 ZerNico

@yusukebe should this issue be closed or converted to a discussion?

rafaell-lycan avatar Oct 01 '23 02:10 rafaell-lycan

@rafaell-lycan

We don't have to close.

Do you mean a "discussion" is one of a GitHub Discussion?

yusukebe avatar Oct 01 '23 20:10 yusukebe

Hello. Since we are discussing open api here, I would like to point out another solution https://github.com/jlalmes/trpc-openapi that requires integration of both swagger and hono.

popuguytheparrot avatar Oct 03 '23 10:10 popuguytheparrot