exemple for zod-openapi + rpc

does anyone has a working exemple of https://hono.dev/examples/zod-openapi + https://hono.dev/docs/guides/rpc i know i have to chain routes, but i still got unknown (maybe because of zod-openapi ?)
Zod OpenAPI - Hono
Web framework built on Web Standards for Cloudflare Workers, Fastly Compute, Deno, Bun, Vercel, Node.js, and others. Fast, but not only fast.
RPC - Hono
Web framework built on Web Standards for Cloudflare Workers, Fastly Compute, Deno, Bun, Vercel, Node.js, and others. Fast, but not only fast.
24 Replies
Arjix
Arjixβ€’4w ago
I've had great success with hono-openapi But unfortunately it doesn't type-check the return type of your routes the same way zod-openapi does
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’4w ago
i saw your post on other thread, maybe i will go for hono-openapi instead (do you ahve simple exemple with hono-openapi + rpc ?)
Arjix
Arjixβ€’4w ago
There is nothing special about that combination Just follow the docs for both of them It'll just work PS: for openapi generation, it will only include routes that have the middleware describeRoute, routes w/o it won't show up on swagger for example
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’4w ago
OK
ambergristle
ambergristleβ€’4w ago
@WAAYZZz πŸ€ if you’re getting unknown, it could be an issue w your implementation, or you might need to use generated types Switching to hono-openapi has its advantages, but it won’t fix your type issue
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’4w ago
i will share my code tomorrows, it's simple implementation for the moment (juste a function returning a hono instance with pino and error handler set, and a zodopenapi route returnin fixed data
ambergristle
ambergristleβ€’4w ago
If you’re applying middleware in a custom function, that may be the problem. I’d recommend taking a look at the Hono factory helper
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’4w ago
import { OpenAPIHono } from "@hono/zod-openapi";

type AppBindings = {
Variables: {
logger: PinoLogger;
};
};

type AppOpenAPI = OpenAPIHono<AppBindings>;

function createInternalApp<T extends Env>() {
const app = new OpenAPIHono<AppBindings & T>();
app.use(pinoLogger());
app.onError((error, c) => {
if (error instanceof HTTPException) {
return error.getResponse();
}
const logger = c.get("logger");
logger.error(error);
return c.json({ message: "Internal server error" }, 500);
});
return app;
}

const userRoutes = createInternalApp().basePath("/user");

// getUserRoute is a simple openApi object
userRoutes.openapi(getUserRoute, async (c) => {
const user = await getUser();
return c.json(user, 200);
});

const app = createInternalApp().route("/", userRoutes);
const client = hc<typeof app>("http://localhost:8787/");
import { OpenAPIHono } from "@hono/zod-openapi";

type AppBindings = {
Variables: {
logger: PinoLogger;
};
};

type AppOpenAPI = OpenAPIHono<AppBindings>;

function createInternalApp<T extends Env>() {
const app = new OpenAPIHono<AppBindings & T>();
app.use(pinoLogger());
app.onError((error, c) => {
if (error instanceof HTTPException) {
return error.getResponse();
}
const logger = c.get("logger");
logger.error(error);
return c.json({ message: "Internal server error" }, 500);
});
return app;
}

const userRoutes = createInternalApp().basePath("/user");

// getUserRoute is a simple openApi object
userRoutes.openapi(getUserRoute, async (c) => {
const user = await getUser();
return c.json(user, 200);
});

const app = createInternalApp().route("/", userRoutes);
const client = hc<typeof app>("http://localhost:8787/");
(code is splited, just grouped for demo) ok i succed by better chaining
const app = createInternalApp().openapi(getUserRoute, async (c) => {
const user = await getUser();
return c.json(user, 200);
});
const app = createInternalApp().openapi(getUserRoute, async (c) => {
const user = await getUser();
return c.json(user, 200);
});
was needed i migrate to hono-openapi and factories and it works
export type AppBindings = {
Variables: {
logger: PinoLogger;
};
};

export default createFactory<AppBindings>({
initApp: (app) => {
app.use(pinoLogger());
app.onError((error, c) => {
if (error instanceof HTTPException) {
return error.getResponse();
}
const logger = c.get("logger");
logger.error(error);
return c.json({ message: "Internal server error" }, 500);
});
},
});

const app = factoryWithLogs.createApp().get(
"/",
describeRoute({
description: "Say hello to the user",
responses: {
200: {
description: "Successful response",
content: {
"text/plain": { schema: resolver(responseSchema) },
},
},
},
}),
zValidator("query", querySchema),
async (c) => {
const query = c.req.valid("query");
const user = await getUser();
return c.json(user, 200);
}
);

export default userApp;

const app = factoryWithLogs.createApp().route("/user", UserApp);
export type AppBindings = {
Variables: {
logger: PinoLogger;
};
};

export default createFactory<AppBindings>({
initApp: (app) => {
app.use(pinoLogger());
app.onError((error, c) => {
if (error instanceof HTTPException) {
return error.getResponse();
}
const logger = c.get("logger");
logger.error(error);
return c.json({ message: "Internal server error" }, 500);
});
},
});

const app = factoryWithLogs.createApp().get(
"/",
describeRoute({
description: "Say hello to the user",
responses: {
200: {
description: "Successful response",
content: {
"text/plain": { schema: resolver(responseSchema) },
},
},
},
}),
zValidator("query", querySchema),
async (c) => {
const query = c.req.valid("query");
const user = await getUser();
return c.json(user, 200);
}
);

export default userApp;

const app = factoryWithLogs.createApp().route("/user", UserApp);
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’4w ago
well encountred an other issue πŸ˜„ https://github.com/rhinobase/hono-openapi/issues/91 but with hono-openapi this time
GitHub
using createFactory to add isn't detected by openapi Β· Issue #91 ...
Hi, import { createFactory } from "hono/factory"; import { describeRoute } from "hono-openapi"; import type { PinoLogger } from "hono-pino"; import { HTTPException } f...
ambergristle
ambergristleβ€’4w ago
What do you mean by β€œdetected”? Also, please avoid opening issues unless you know there’s a problem with the library
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’4w ago
Doesn't appear in the openapi export
ambergristle
ambergristleβ€’4w ago
You mean /a/test doesn’t show up in the openapi spec generated at /openapi?
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’4w ago
Absolutely, but using new hono instead of createfacorry makes it show up
ambergristle
ambergristleβ€’4w ago
Interesting. I’ll try and repro What runtime are you using?
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’4w ago
Nodejs i think problem is related to use factory with initApp
ambergristle
ambergristleβ€’4w ago
interesting. @WAAYZZz πŸ€ i can repro the error. looks like it's the onError registration that's causing the issue this is the line in hono-openapi that's preventing the docs from being generated: https://github.com/rhinobase/hono-openapi/blob/7f5d0affeb538045e3718933a550b00381ceb9bc/packages/core/src/openapi.ts#L93 not sure what it is about registering onError in createApp that interferes with the uniqueSymbol
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’4w ago
wow well played
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’4w ago
last question, how to properly pass rpc client or typeof App to frontend in a monorepo architecture ? i have this repo https://github.com/iNeoO/sirena in apps/backend/src/hc.ts client or AppType are well typed but in apps/frontend/src/lib/api/hc.ts everything becomes any the only way i manged to make it works it's to not use factory, new app creation
GitHub
GitHub - iNeoO/sirena
Contribute to iNeoO/sirena development by creating an account on GitHub.
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’4w ago
and even doing that, the first level of routes are well typed, but not the second (eg: user)
ambergristle
ambergristleβ€’4w ago
You’ll want to generate types during transpilation and use those with the client instead You might need to update your tsconfig
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’4w ago
I'm trying this thx, used pkgroll and it works now
ambergristle
ambergristleβ€’4w ago
i figured out what the issue is with onError i added the details to the issue you opened for reference i'm actually not sure whether i'd consider this a bug/edge-case its certainly an unexpected behavior
WAAYZZz πŸ€
WAAYZZz πŸ€OPβ€’4w ago
thx for your huge help
ambergristle
ambergristleβ€’4w ago
happy to help!

Did you find this page helpful?