H
Honoβ€’2mo ago
WAAYZZz πŸ€

Use zod schema generated with prisma with @hono/zod-openapi

Is there a way to use already generated zod schema with @hono/zod-openapi ? The Zod schemas generated by Prisma are not enhanced the way @hono/zod-openapi provides. Does anyone know a way to enhance them for use with createRoute from @hono/zod-openapi (request.params, json validator, ...)
42 Replies
ambergristle
ambergristleβ€’2mo ago
you could try using zod-openapi as used withhono-openapi: https://github.com/rhinobase/hono-openapi?tab=readme-ov-file#basic-usage
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’2mo ago
resolver doesn t work, and i can't use import "zod-openapi/extend"; since code is already generated I didn't find a solution ATM, I'm creating my own zod type and not using ones generated by prisma
ambergristle
ambergristleβ€’2mo ago
when you say code is already generated, you're talking about the schemas, no?
import "zod-openapi/extend";
import { GeneratedSchema } from './prisma-schemas';

const OpenApiSchema = GeneratedSchema
.openapi({ ref: 'Example' });
import "zod-openapi/extend";
import { GeneratedSchema } from './prisma-schemas';

const OpenApiSchema = GeneratedSchema
.openapi({ ref: 'Example' });
oh, you're saying that the zod instance of the imported schemas won't be extended
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’2mo ago
yes, i m using zod-prisma-types
ambergristle
ambergristleβ€’2mo ago
you could do something like this then
import "zod-openapi/extend";
import { GeneratedSchema } from './prisma-schemas';

const OpenApiSchema = z.object(GeneratedSchema.shape)
.openapi({ ref: 'Example' });
import "zod-openapi/extend";
import { GeneratedSchema } from './prisma-schemas';

const OpenApiSchema = z.object(GeneratedSchema.shape)
.openapi({ ref: 'Example' });
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’2mo ago
This way, it works ! Thx !! (i m using import { z } from "@hono/zod-openapi"; directly) (i still have a strange problem but i will deal with it)

export const PlanSchema = z.object(zod.PlanSchema.shape);

// Tricks because GetPlanParamsSchema as request.params not working
// export const GetPlanParamsSchema = PlanSchema.pick({
// id: true,
// });
export const GetPlanParamsSchema = z.object({
id: z.string(),
});

const getPlanRoute = createRoute({
method: "get",
path: "/{id}",
tags: ["plan"],
request: {
params: GetPlanParamsSchema,
},
responses: {
200: {
description: "Response after getting plan",
content: {
"application/json": {
schema: GetPlanReturnSchema,
},
},
},
...error403ForbiddenResponse,
...error404PlanNotFound,
},
});

export const PlanSchema = z.object(zod.PlanSchema.shape);

// Tricks because GetPlanParamsSchema as request.params not working
// export const GetPlanParamsSchema = PlanSchema.pick({
// id: true,
// });
export const GetPlanParamsSchema = z.object({
id: z.string(),
});

const getPlanRoute = createRoute({
method: "get",
path: "/{id}",
tags: ["plan"],
request: {
params: GetPlanParamsSchema,
},
responses: {
200: {
description: "Response after getting plan",
content: {
"application/json": {
schema: GetPlanReturnSchema,
},
},
},
...error403ForbiddenResponse,
...error404PlanNotFound,
},
});

use the commented GetPlanParamsSchema crash the openapi generation but i can deal with that
ambergristle
ambergristleβ€’2mo ago
Huh. Crash how? What’s the error message?
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’2mo ago
/doc return {} crashing the swagger ui (only logs i have for this)
plan-backend-dev | [16:16:39.896] INFO (1190): Request completed
rplan-backend-dev | res: {
rplan-backend-dev | "status": 500,
rplan-backend-dev | "headers": {}
rplan-backend-dev | }
plan-backend-dev | [16:16:39.896] INFO (1190): Request completed
rplan-backend-dev | res: {
rplan-backend-dev | "status": 500,
rplan-backend-dev | "headers": {}
rplan-backend-dev | }
ambergristle
ambergristleβ€’2mo ago
hm. can you add some backend/error handler logs? what happens if you hit the openapi docs endpoint?
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’2mo ago
/doc return a status 500 with only {} in response
No description
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’2mo ago
running out of docker doesn t reproduce the pb, strange behavior
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’2mo ago
If u really want to give a look, code is available her, readme is not up to date https://github.com/iNeoO/rplan/tree/feat/archi
GitHub
GitHub - iNeoO/rplan at feat/archi
Contribute to iNeoO/rplan development by creating an account on GitHub.
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’2mo ago
(request.params: InvitationTokenSchema in packages/backend/src/routes/invitation.route.ts is causing the pb)
ambergristle
ambergristleβ€’2mo ago
what is a "pb"? there aren't any obvious code/type issues
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’2mo ago
yeah that's really strange and the fact the pb only happen in docker container :/
ambergristle
ambergristleβ€’2mo ago
again, what is "pb"? your repo is too complicated for me to spin up locally. i'm happy to try running a minimal example if you can share one otherwise, i recommend adding some error logs to your backend. there's not much we can diagnose without them
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’2mo ago
so i'm using docker compose to cluster the application my zod prisma types are generated in dockerfile and imported in backed as package with pnpm monorepo way.
// invitation.schema.ts
import zod from "@rplan/database/generated/zod/index.ts";

const InvitationSchema = z.object(zod.InvitationSchema.shape);

// Tricks because InvitationTestSchema as request.params not working
export const InvitationTokenSchema = InvitationSchema.pick({
token: true,
});
// export const InvitationTokenSchema = z.object({
// token: z.string(),
// });

// invitation.route.ts

import {
InvitationTokenSchema,
AcceptInvitationResponse,
} from "../schemas/invitation.schema.ts";

import { createRoute } from "@hono/zod-openapi";

const acceptInvitationRoute = createRoute({
method: "post",
path: "/{token}",
tags: ["invitation"],
request: {
// this make /doc crash
// using doesn't break the /doc
// z.object({
// token: z.string(),
// });
params: InvitationTokenSchema,
},
responses: {
200: {
description: "Response after accepting link invitation",
content: {
"application/json": {
schema: AcceptInvitationResponse,
},
},
},
400: {
description: "Response after creating a plan",
content: {
"application/json": {
schema: ErrorSchema,
},
},
},
...error401UnauthorizedResponse,
},
});
// invitation.schema.ts
import zod from "@rplan/database/generated/zod/index.ts";

const InvitationSchema = z.object(zod.InvitationSchema.shape);

// Tricks because InvitationTestSchema as request.params not working
export const InvitationTokenSchema = InvitationSchema.pick({
token: true,
});
// export const InvitationTokenSchema = z.object({
// token: z.string(),
// });

// invitation.route.ts

import {
InvitationTokenSchema,
AcceptInvitationResponse,
} from "../schemas/invitation.schema.ts";

import { createRoute } from "@hono/zod-openapi";

const acceptInvitationRoute = createRoute({
method: "post",
path: "/{token}",
tags: ["invitation"],
request: {
// this make /doc crash
// using doesn't break the /doc
// z.object({
// token: z.string(),
// });
params: InvitationTokenSchema,
},
responses: {
200: {
description: "Response after accepting link invitation",
content: {
"application/json": {
schema: AcceptInvitationResponse,
},
},
},
400: {
description: "Response after creating a plan",
content: {
"application/json": {
schema: ErrorSchema,
},
},
},
...error401UnauthorizedResponse,
},
});
running the application direcylu with pnpm doesn't break the /doc but thx for your help, even if it's not the perfect way, you provide me a way to partial solve the problem
ambergristle
ambergristleβ€’2mo ago
for reference, you can add syntax highlighting to discord code snippets it uses markdown syntax, so just add the language after the opening backticks: ```typescript you really should add an error handler with an error log to your backend. that's an mvp requirement
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’2mo ago
i had a error catch handler but no more logs, i think my error could be related to this https://github.com/honojs/middleware/issues/865
GitHub
Zod OpenAPI Hono - .catch make all the openapi doc resolve to an em...
Issue description When working with the middleware and adding a catch handler to a route's description, I observed that the OpenAPI documentation fails to generate properly and results in a 500...
ambergristle
ambergristleβ€’2mo ago
using a catch block on a schema like that seems like an anti-pattern but you could try switching to hono-openapi. its more flexible wrt implementation
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’2mo ago
ok i will give a try thx for your time
// index.ts
const app = createInternalApp();

app.route("auth", authRoutes);
app.route("user", userRoutes);
app.route("plan", planRoutes);
app.route("invitation", invitationRoutes);

app.get(
"/openapi",
openAPISpecs(app, {
documentation: {
info: {
title: "Rplan API",
version: "1.0.0",
description: "API for Rplan",
},
},
})
);

app.get(
"/ui",
swaggerUI({
url: "/openapi",
})
);
// index.ts
const app = createInternalApp();

app.route("auth", authRoutes);
app.route("user", userRoutes);
app.route("plan", planRoutes);
app.route("invitation", invitationRoutes);

app.get(
"/openapi",
openAPISpecs(app, {
documentation: {
info: {
title: "Rplan API",
version: "1.0.0",
description: "API for Rplan",
},
},
})
);

app.get(
"/ui",
swaggerUI({
url: "/openapi",
})
);
Do you know if there is something special todo to handle app.route for openapi ?
ambergristle
ambergristleβ€’2mo ago
meaning like a specific method, or some config you need to get routing to work? (the way you use .openapi for the handlers)
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’2mo ago
if i don t use app.get / post at the root of app, routes aren t visible by openApiSpecs
ambergristle
ambergristleβ€’2mo ago
meaning that
const app = createInternalApp();

app.get(/** */) // this shows up in the openapi spec
app.route('/auth', authRoutes) // and this doesn't?
const app = createInternalApp();

app.get(/** */) // this shows up in the openapi spec
app.route('/auth', authRoutes) // and this doesn't?
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’2mo ago
exactly
ambergristle
ambergristleβ€’2mo ago
have you tried adding a slash in front of the route name, e.g., /auth instead of auth i wouldn't expect it to work without the slash, even on vanilla hono
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’2mo ago
now yes but routes works but aren't showing in openapi swagger
ambergristle
ambergristleβ€’2mo ago
what happens when you visit /openapi? do the docs get generated, or nah
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’2mo ago
it s like i have no routes
{"openapi":"3.1.0","info":{"title":"Rplan API","description":"API for Rplan","version":"1.0.0"},"paths":{},"components":{"schemas":{}}}
{"openapi":"3.1.0","info":{"title":"Rplan API","description":"API for Rplan","version":"1.0.0"},"paths":{},"components":{"schemas":{}}}
if i create a route with app.get at root of the application, route is detected by openapi
ambergristle
ambergristleβ€’2mo ago
curious
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’2mo ago
[Symbol(openapi)] is missing in my app.route routes
ambergristle
ambergristleβ€’2mo ago
when you say root, do you mean
const app = createInternalApp();
app.get(/** */);
// OR
const app = createInternalApp()
get(/** */);
const app = createInternalApp();
app.get(/** */);
// OR
const app = createInternalApp()
get(/** */);
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’2mo ago
first case
ambergristle
ambergristleβ€’2mo ago
are you using the openapi method on your schemas?
const PlanSchema = z.object(zod.PlanSchema.shape).openapi('PlanSchema')
const PlanSchema = z.object(zod.PlanSchema.shape).openapi('PlanSchema')
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’2mo ago
no just default z.object if i copy the route working at root in a sub route she doesn t work
ambergristle
ambergristleβ€’2mo ago
curious like so?
const route = createInternalApp();
route.get(/** */) // this doesn't work

const app = createInternalApp();
app.get(/** */); // this works
app.route('/example', route); // this is breaking?
const route = createInternalApp();
route.get(/** */) // this doesn't work

const app = createInternalApp();
app.get(/** */); // this works
app.route('/example', route); // this is breaking?
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’2mo ago
not breaking but not interpreted
ambergristle
ambergristleβ€’2mo ago
but that's what you meant by "route that works at root doesn't work in sub route" hmmm have you tried
app.openapi('/sub-route', subroute)
app.openapi('/sub-route', subroute)
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’2mo ago
app.openapi doesn t exist i got it it's because of my honoCreateApp, i need to handle openapi inside using const app = new Hono(); instead of const app = createInternalApp(); works ok strange behavior it's because i wasn't providing a basePath when creating app
ambergristle
ambergristleβ€’2mo ago
huh my recommendation would be to try and create an isolated + minimal example, and add complexity until it breaks it looks like you're doing a fair bit of abstraction, which makes it sort of hard to debug, and is known to cause problems with how hono works (though that's mainly for typing) if nothing else, it will make it easier to spot if/where you're departing from the @hono/zod-openapi docs/recommended approach
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’2mo ago
yes i will try that, thx for your help ! do you have any repo to recommand ?
ambergristle
ambergristleβ€’2mo ago
for a @hono/zod-openapi app? no, i'm afraid not. i haven't worked much w it tbh folks have definitely shared some in #showcase , and in various chats on this server i would search the discord for @hono/zod-openapi

Did you find this page helpful?