H
Hono3mo ago
codewansh

@hono/zod-openapi Middleware Issue

Hi there, I need some help in implementing middleware while using hono and @hono/zod-openapi. What I want to implement is running middleware after zod validation of query params but before the actual route handler. My code is as follows: This is the route code
import { createRouter } from "@/lib/create-app";
import { authMiddleware } from "@/middlewares/auth";

import * as handlers from "./state.handlers";
import * as routes from "./state.routes";

const router = createRouter();
router.openapi(routes.list, handlers.list);

export default router;
import { createRouter } from "@/lib/create-app";
import { authMiddleware } from "@/middlewares/auth";

import * as handlers from "./state.handlers";
import * as routes from "./state.routes";

const router = createRouter();
router.openapi(routes.list, handlers.list);

export default router;
This is route initialization code
import { OpenAPIHono } from "@hono/zod-openapi";
import { notFound, onError, serveEmojiFavicon } from "stoker/middlewares";
import { defaultHook } from "stoker/openapi";
import type { AppBindings, AppOpenAPI } from "./types";

export function createRouter() {
return new OpenAPIHono<AppBindings>({
strict: false,
defaultHook,
});
}

export default function createApp() {
const app = createRouter();
app.use(serveEmojiFavicon("📝"));
app.use("/api/*", cors());

app.notFound(notFound);
app.onError(onError);
return app;
}
import { OpenAPIHono } from "@hono/zod-openapi";
import { notFound, onError, serveEmojiFavicon } from "stoker/middlewares";
import { defaultHook } from "stoker/openapi";
import type { AppBindings, AppOpenAPI } from "./types";

export function createRouter() {
return new OpenAPIHono<AppBindings>({
strict: false,
defaultHook,
});
}

export default function createApp() {
const app = createRouter();
app.use(serveEmojiFavicon("📝"));
app.use("/api/*", cors());

app.notFound(notFound);
app.onError(onError);
return app;
}
9 Replies
codewansh
codewanshOP3mo ago
This is the route definition
import { createRoute, z } from "@hono/zod-openapi";
import * as HttpStatusCodes from "stoker/http-status-codes";
import { jsonContent } from "stoker/openapi/helpers";


export const list = createRoute({
path: "/state",
method: "get",
request: {
query: z.object({
CountryId: z.string().optional().default("US").openapi({
description: "Two char country abbreviation for filtering returned data set by country.",
examples: ["US", "CA", "UK"],
}),
returnInfo: z.enum(["true", "false"]).optional().default("false").transform(val => val === "true").openapi({
description: "Returns an additional information field",
example: "false",
}),
}),
},
responses: {
[HttpStatusCodes.OK]: jsonContent(
z.array(z.object({
StateId: z.number().openapi({
example: 1,
}),
Info: z.string().optional().nullish().openapi({
example: "This state may be missing available information",
}),
LastUpdatedDateTime: z.string().openapi({
example: "2022-09-11T22:10:50Z",
}),
})),
),
},
});

export type ListRoute = typeof list;
import { createRoute, z } from "@hono/zod-openapi";
import * as HttpStatusCodes from "stoker/http-status-codes";
import { jsonContent } from "stoker/openapi/helpers";


export const list = createRoute({
path: "/state",
method: "get",
request: {
query: z.object({
CountryId: z.string().optional().default("US").openapi({
description: "Two char country abbreviation for filtering returned data set by country.",
examples: ["US", "CA", "UK"],
}),
returnInfo: z.enum(["true", "false"]).optional().default("false").transform(val => val === "true").openapi({
description: "Returns an additional information field",
example: "false",
}),
}),
},
responses: {
[HttpStatusCodes.OK]: jsonContent(
z.array(z.object({
StateId: z.number().openapi({
example: 1,
}),
Info: z.string().optional().nullish().openapi({
example: "This state may be missing available information",
}),
LastUpdatedDateTime: z.string().openapi({
example: "2022-09-11T22:10:50Z",
}),
})),
),
},
});

export type ListRoute = typeof list;
And this is the handler
import * as HttpStatusCodes from "stoker/http-status-codes";

import type { AppRouteHandler } from "@/lib/types";

import type { ListRoute } from "@/routes/json_v1.6/state/state.routes";


export const list: AppRouteHandler<ListRoute> = async (c) => {
const { CountryId, returnInfo } = c.req.valid("query");

return c.json({}, HttpStatusCodes.OK, {
"Content-Type": 'application/json',
});
};
import * as HttpStatusCodes from "stoker/http-status-codes";

import type { AppRouteHandler } from "@/lib/types";

import type { ListRoute } from "@/routes/json_v1.6/state/state.routes";


export const list: AppRouteHandler<ListRoute> = async (c) => {
const { CountryId, returnInfo } = c.req.valid("query");

return c.json({}, HttpStatusCodes.OK, {
"Content-Type": 'application/json',
});
};
And these are the types
import type { OpenAPIHono, RouteConfig, RouteHandler, RouteHook } from "@hono/zod-openapi";
import type { PinoLogger } from "hono-pino";

export interface AppBindings {
Variables: {
logger: PinoLogger;
};
};

export type AppOpenAPI = OpenAPIHono<AppBindings>;

export type AppRouteHandler<R extends RouteConfig> = RouteHandler<R, AppBindings>;
import type { OpenAPIHono, RouteConfig, RouteHandler, RouteHook } from "@hono/zod-openapi";
import type { PinoLogger } from "hono-pino";

export interface AppBindings {
Variables: {
logger: PinoLogger;
};
};

export type AppOpenAPI = OpenAPIHono<AppBindings>;

export type AppRouteHandler<R extends RouteConfig> = RouteHandler<R, AppBindings>;
To summarize I want to get access to c.req.valid("query"); inside a middleware (running before the handler). If it is possible like this then it would be awesome. router.openapi(routes.list,authMiddleware, handlers.list);
ambergristle
ambergristle3mo ago
fwiw, this level of abstraction doesn't play all that nicely with hono, and ultimately makes code maintenance more difficult but to make intermediate middleware aware of validation types you can use the createMiddleware generic types e.g.,
createMiddleware<{}, '/', {
in: {
json: {
test: string;
};
};
out: {
json: {
test: string;
};
};
}>(async (c, next) => {
// ...
})
createMiddleware<{}, '/', {
in: {
json: {
test: string;
};
};
out: {
json: {
test: string;
};
};
}>(async (c, next) => {
// ...
})
the in param doesn't really matter afaik, but out should specify the resolved type of the validated target data
codewansh
codewanshOP3mo ago
But is there a way to infer it from ListRoute type similar to how AppRouteHandler Generic works?
ambergristle
ambergristle3mo ago
no. there's no cross-middleware inference, unless you inline it that's the cost of abstraction in hono
codewansh
codewanshOP3mo ago
That sad to hear 🥲 But thanks for the input
ambergristle
ambergristle3mo ago
There’s an open discussion RE adding support for the kind of inference you’re looking for, but it would likely involve overhauling Hono’s type system, so it won’t happen any time soon
codewansh
codewanshOP3mo ago
Can you post the link for it?
ambergristle
ambergristle3mo ago
I don’t have it locked and loaded. I’m sure it will come up if you search GH issues for middleware typing though
codewansh
codewanshOP3mo ago
Ok thanks

Did you find this page helpful?