Gathering detailed insights and metrics for hono-zod-openapi
Gathering detailed insights and metrics for hono-zod-openapi
Gathering detailed insights and metrics for hono-zod-openapi
Gathering detailed insights and metrics for hono-zod-openapi
Alternative Hono middleware for creating OpenAPI documentation from Zod schemas
npm install hono-zod-openapi
Typescript
Module System
Node Version
NPM Version
71.2
Supply Chain
98.4
Quality
81.8
Maintenance
100
Vulnerability
100
License
TypeScript (100%)
Total Downloads
6,972
Last Day
22
Last Week
178
Last Month
731
Last Year
6,972
MIT License
24 Stars
46 Commits
1 Forks
1 Watchers
3 Branches
1 Contributors
Updated on Feb 22, 2025
Latest Version
0.5.0
Package Id
hono-zod-openapi@0.5.0
Unpacked Size
32.31 kB
Size
9.94 kB
File Count
5
NPM Version
10.8.2
Node Version
20.18.0
Published on
Nov 13, 2024
Cumulative downloads
Total Downloads
Last Day
-4.3%
22
Compared to previous day
Last Week
27.1%
178
Compared to previous week
Last Month
-5.9%
731
Compared to previous month
Last Year
0%
6,972
Compared to previous year
2
Alternative Hono middleware for creating OpenAPI documentation from Zod schemas
npm install hono-zod-openapi hono zod
Or, if you prefer JSR:
jsr add @paolostyle/hono-zod-openapi
Hono provides a 3rd-party middleware in their middleware monorepo, which probably works alright, however my issue with this package is that it forces you to write your code in a different manner than you normally would in Hono. Refactoring the app becomes a significant hassle.
This library provides an openApi
middleware instead, which you can easily add to your existing codebase, and a createOpenApiDocument
function,
which will generate an OpenAPI-compliant JSON document and serve it under /doc
route of your app (it's configurable, don't worry).
@hono/zod-validator
(we're using it as a dependency)⚠ Warning: This library is still at the early stages although I would consider the documented API rather stable since version 0.2.0. In any case, please be aware that until I release v1.0.0 there might still be some breaking changes between minor versions.
This library is based on zod-openapi
library (not the same one as the official package).
zod-openapi
provides an extension to Zod, which adds a new .openapi()
method.
hono-zod-openapi
reexports the extendZodWithOpenApi
method. Call it ideally somewhere in your entry point (e.g. in a file with your main Hono router).
1import { z } from 'zod'; 2import { extendZodWithOpenApi } from 'hono-zod-openapi'; 3 4extendZodWithOpenApi(z); 5 6z.string().openapi({ description: 'hello world!', example: 'hello world' });
This is not strictly necessary, but it allows you to add additional information that cannot be represented by just using Zod, or registering OpenAPI components.
hono-zod-openapi
provides a middleware which you can attach to any endpoint.
It accepts a single argument, an object that is mostly the same as the OpenAPI Operation Object.
There are 2 main differences:
a request
field, which functions essentially like a condensed version of @hono/zod-validator
.
For example, { json: z.object() }
is equivalent to zValidator('json', z.object())
. Passing multiple attributes to the object is equivalent to
calling multiple zValidator
s.
At the same time, it will translate the validation schemas to OpenAPI notation.
There is no need to use parameters
and requestBody
fields at all (but it is still possible).
enhanced responses
field which has essentially 4 variants:
Passing a Zod schema directly. For simple APIs this is more than enough. description
field
will be equal to the full HTTP status code (e.g. 200 OK
or 500 Internal Server Error
) and
media type will be inferred based on the passed schema, though it is pretty simple now - for
z.string()
it will be text/plain
, otherwise it's application/json
.
Example:
1openApi({ 2 responses: { 3 200: z 4 .object({ wow: z.string() }) 5 .openapi({ example: { wow: 'this is cool!' } }), 6 }, 7});
1{ 2 "responses": { 3 "200": { 4 "description": "200 OK", 5 "content": { 6 "application/json": { 7 "schema": { 8 "example": { 9 "wow": "this is cool!" 10 }, 11 "properties": { 12 "wow": { 13 "type": "string" 14 } 15 }, 16 "required": ["wow"], 17 "type": "object" 18 } 19 } 20 } 21 } 22 } 23}
"Library notation" - a simplified, flattened format, similar to the official OpenAPI spec, but reduces annoying nesting. Convenient form if you want a custom description or need to pass extra data.
Example:
1openApi({ 2 responses: { 3 200: { 4 // the only required field! Use .openapi() method on the schema to add metadata 5 schema: z.string().openapi({ 6 description: 'HTML code', 7 example: '<html><body>hi!</body></html>', 8 }), 9 // description is optional, as opposed to OpenAPI spec 10 description: 'My description', 11 // mediaType is optional, it's `text/plain` if schema is z.string() 12 // otherwise it's `application/json`, in other scenarios it should be specified 13 mediaType: 'text/html', 14 // headers field is also optional, but you can also use Zod schema here 15 headers: z.object({ 'x-custom': z.string() }), 16 // ...you can also pass all the other fields you normally would here in OpenAPI spec 17 }, 18 }, 19});
1{ 2 "responses": { 3 "200": { 4 "content": { 5 "text/html": { 6 "schema": { 7 "description": "HTML code", 8 "example": "<html><body>hi!</body></html>", 9 "type": "string" 10 } 11 } 12 }, 13 "description": "My description", 14 "headers": { 15 "x-custom": { 16 "required": true, 17 "schema": { 18 "type": "string" 19 } 20 } 21 } 22 } 23 } 24}
zod-openapi
notation. Mostly useful when you need to have content
in multiple formats,
or you just want to be as close as possible to the official spec.
Example:
1openApi({
2 responses: {
3 200: {
4 // required
5 description: 'Success response',
6 content: {
7 'application/json': {
8 schema: z.object({ welcome: z.string() }),
9 },
10 },
11 // ...you can also pass all the other fields you normally would here in OpenAPI spec
12 },
13 },
14});
1{ 2 "responses": { 3 "200": { 4 "content": { 5 "application/json": { 6 "schema": { 7 "properties": { 8 "welcome": { 9 "type": "string" 10 } 11 }, 12 "required": ["welcome"], 13 "type": "object" 14 } 15 } 16 }, 17 "description": "Success response" 18 } 19 } 20}
Classic OpenAPI spec notation: just refer to the official spec. Not recommended but it also just works.
Since the object can get pretty large, you can use defineOpenApiOperation
function to get
the autocomplete in the IDE.
Simple example:
1import { Hono } from 'hono'; 2import { z } from 'zod'; 3import { createOpenApiDocument, openApi } from 'hono-zod-openapi'; 4 5export const app = new Hono().get( 6 '/user', 7 openApi({ 8 tags: ['User'], 9 responses: { 10 200: z.object({ hi: z.string() }).openapi({ example: { hi: 'user' } }), 11 }, 12 request: { 13 query: z.object({ id: z.string() }), 14 }, 15 }), 16 (c) => { 17 // works identically to @hono/zod-validator 18 const { id } = c.req.valid('query'); 19 return c.json({ hi: id }, 200); 20 }, 21); 22 23// this will add a `GET /doc` route to the `app` router 24createOpenApiDocument(app, { 25 info: { 26 title: 'Example API', 27 version: '1.0.0', 28 }, 29});
1{ 2 "info": { 3 "title": "Example API", 4 "version": "1.0.0" 5 }, 6 "openapi": "3.1.0", 7 "paths": { 8 "/user": { 9 "get": { 10 "tags": ["User"], 11 "parameters": [ 12 { 13 "in": "query", 14 "name": "id", 15 "required": true, 16 "schema": { 17 "type": "string" 18 } 19 } 20 ], 21 "responses": { 22 "200": { 23 "content": { 24 "application/json": { 25 "schema": { 26 "example": { 27 "hi": "user" 28 }, 29 "properties": { 30 "hi": { 31 "type": "string" 32 } 33 }, 34 "required": ["hi"], 35 "type": "object" 36 } 37 } 38 }, 39 "description": "200 OK" 40 } 41 } 42 } 43 } 44 } 45}
Generally you just need to follow one of the Authentication guides here, depending on the type of authentication you're using.
Bearer Auth example:
1const app = new Hono().get(
2 '/example',
3 openApi({
4 responses: {
5 200: z.object({}),
6 },
7 security: [{ bearerAuth: [] }],
8 }),
9 async (c) => {
10 return c.json({}, 200);
11 },
12);
13
14createOpenApiDocument(app, {
15 info: {
16 title: 'Some API',
17 version: '0.0.1',
18 },
19 components: {
20 securitySchemes: {
21 bearerAuth: {
22 type: 'http',
23 scheme: 'bearer',
24 },
25 },
26 },
27 // if you use bearer auth in every endpoint, you can add
28 // this here instead of adding `security` to every route:
29 // security: [{ bearerAuth: [] }],
30});
Adding the same fields to various routes over an over can be a bit tedious. You can create your own typesafe wrapper,
which will provide the fields shared by multiple endpoints. For example, if a lot of your endpoints require a security
field
and a tag
, you can create a function like this:
1const taggedAuthRoute = <T extends HonoOpenApiRequestSchemas>( 2 doc: HonoOpenApiOperation<T>, 3) => { 4 return defineOpenApiOperation({ 5 ...doc, 6 tags: ['MyTag'], 7 security: [{ apiKey: [] }], 8 }); 9};
and use it with openApi
middleware:
1openApi(
2 taggedAuthRoute({
3 request: {
4 json: z.object({ field: z.number() }),
5 },
6 }),
7);
createOpenApiDocument
1function createOpenApiDocument(
2 router: Hono,
3 document: Omit<ZodOpenApiObject, 'openapi'>,
4 { addRoute = true, routeName = '/doc' }: Settings = {},
5): ReturnType<typeof createDocument>;
Call this function after you defined your Hono app to generate the OpenAPI document and host it under /doc
route by default. info
field in the second argument is required by the OpenAPI specification. You can pass there also any other fields available in the OpenAPI specification, e.g. servers
, security
or components
.
Examples:
typical usage:
1createOpenApiDocument(app, { 2 info: { 3 title: 'Example API', 4 version: '1.0.0', 5 }, 6});
add the route under /openApi route:
1createOpenApiDocument( 2 app, 3 { 4 info: { 5 title: 'Example API', 6 version: '1.0.0', 7 }, 8 }, 9 { routeName: '/openApi' }, 10);
don't add the route, just get the OpenAPI document as an object
1const openApiDoc = createOpenApiDocument( 2 app, 3 { 4 info: { 5 title: 'Example API', 6 version: '1.0.0', 7 }, 8 }, 9 { addRoute: false }, 10);
openApi
1function openApi<Req extends RequestSchemas, E extends Env, P extends string>( 2 operation: Operation<Req>, 3): MiddlewareHandler<E, P, Values<Req>>;
A Hono middleware used to document a given endpoint. Refer to the Middleware section above to see the usage examples.
defineOpenApiOperation
A no-op function, used to ensure proper validator's type inference and provide autocomplete in cases where you don't want to define the spec inline.
Example:
1const operation = defineOpenApiOperation({ 2 responses: { 3 200: z.object({ name: z.string() }), 4 }, 5 request: { 6 json: z.object({ email: z.string() }), 7 }, 8}); 9 10const app = new Hono().post('/user', openApi(operation), async (c) => { 11 const { name } = c.req.valid('json'); 12 13 return c.json({ name }, 200); 14});
1{ 2 "info": { 3 "title": "Example API", 4 "version": "1.0.0" 5 }, 6 "openapi": "3.1.0", 7 "paths": { 8 "/user": { 9 "get": { 10 "parameters": [ 11 { 12 "in": "cookie", 13 "name": "session", 14 "required": true, 15 "schema": { 16 "type": "string" 17 } 18 } 19 ], 20 "requestBody": { 21 "content": { 22 "application/json": { 23 "schema": { 24 "properties": { 25 "email": { 26 "type": "string" 27 } 28 }, 29 "required": ["email"], 30 "type": "object" 31 } 32 } 33 } 34 }, 35 "responses": { 36 "200": { 37 "content": { 38 "application/json": { 39 "schema": { 40 "properties": { 41 "name": { 42 "type": "string" 43 } 44 }, 45 "required": ["name"], 46 "type": "object" 47 } 48 } 49 }, 50 "description": "200 OK" 51 }, 52 "400": { 53 "content": { 54 "application/xml": { 55 "schema": { 56 "properties": { 57 "message": { 58 "type": "string" 59 } 60 }, 61 "required": ["message"], 62 "type": "object" 63 } 64 } 65 }, 66 "description": "Custom description" 67 }, 68 "401": { 69 "content": { 70 "application/json": { 71 "schema": { 72 "properties": { 73 "message": { 74 "type": "string" 75 } 76 }, 77 "required": ["message"], 78 "type": "object" 79 } 80 } 81 }, 82 "description": "Required description" 83 }, 84 "404": { 85 "content": { 86 "application/json": { 87 "schema": { 88 "properties": { 89 "message": { 90 "type": "string" 91 } 92 }, 93 "required": ["message"], 94 "type": "object" 95 } 96 } 97 }, 98 "description": "Not found" 99 } 100 }, 101 "tags": ["User"] 102 } 103 } 104 } 105}
While this package should work in Bun, Deno, Cloudflare Workers and browsers (as I'm not using any platform specific APIs and I do not plan to), the codebase is currently tested against Node.js 18.x, 20.x and 22.x. I haven't found any tools that would help with cross-platform testing that wouldn't incur significant maintenance burden.
For now I managed to successfully run the tests with Bun test runner with some grepping and used the lib in Cloudflare Workers and everything seemed to work fine. If you are using the library in non-Node runtime and encountered some bugs, please consider creating an issue.
No vulnerabilities found.
No security vulnerabilities found.