Noh | エンジニア向け情報共有コミュニティ
Signup / Login

Hono + zod-openapi でレスポンスに余計なfieldを含めても型エラーにならない問題

投稿日: 2025/08/31

更新日: 2025/09/02

結論

Issueで提案されてる実行時検証が良い

余計なfieldを含んでいても型エラーにならない問題

Typescript と Hono と openapi-zod を使っているときの話です。
余計なfieldを含んでいても型エラーにならない問題がります。余計なkeyと言ってもいいかも。

以下のissueで報告されています。

https://github.com/honojs/middleware/issues/913

例えばopenapi-zodで以下のschemaを定義しているとします。idだけを持つレスポンスを返すとします。

const UserResponseSchema = z .object({ id: z.string(), }) .openapi("UserResponseSchema")

これを使ったAPIのエンドポイントを追加するとき、余計なfieldを含めてみましょう。今回はpasswordを追加しています。Hono openapi-zodのレスポンスの型はゆるくて型エラーになりません。

app.openapi( createRoute({ method: "get", path: "/users/1", responses: { 200: { content: { "application/json": { schema: UserResponseSchema, }, }, description: "return a user", }, }, }), async (c) => { const responseUser = { id: "1", password: "xxxx", } return c.json(responseUser, 200) }, )

これでは間違ったfieldが含まれたときに気付けなくて困ります。
せっかくTypescriptを使っているので型エラーで検知したいです。

ちなみにschemaに定義しているfieldが足りないときは型エラーになります。

issueで提案されてる方法

zodでパースして実行時に余計なfieldが含まれているときエラーにするコードが提案されています。

import type { Context } from "hono"; import type { ContentfulStatusCode } from "hono/utils/http-status"; import type { z, ZodSchema } from "zod"; export function strictJSONResponse< C extends Context, S extends ZodSchema, D extends Parameters<Context["json"]>[0] & z.infer<S>, U extends ContentfulStatusCode >(c: C, schema: S, data: D, statusCode?: U) { const validatedResponse = schema.safeParse(data); if (!validatedResponse.success) { return c.json( { message: "Strict response validation failed", }, 500 ); } return c.json(validatedResponse.data, statusCode); }

引用: https://github.com/honojs/middleware/issues/913#issuecomment-2889258966

openapi-zodのschemaは.strict()してる必要があります。

使い方はこんな感じかな

return strictJSONResponse( c, z .object({ id: z.string(), }) .strict(), { id: "1" }, 200, )

試したけどだめだった方法

レスポンスのschemaから型を作成し、オブジェクトを検証する。
以下のようなイメージです。

import { createRoute, OpenAPIHono } from "@hono/zod-openapi" import { z } from "@hono/zod-openapi" const app= new OpenAPIHono() const UserResponseSchema = z .object({ id: z.string(), }) .openapi("UserResponseSchema") app.openapi( createRoute({ method: "get", path: "/users/1", responses: { 200: { content: { "application/json": { schema: UserResponseSchema, }, }, description: "return a user", }, }, }), async (c) => { const responseUser: z.infer<typeof UserResponseSchema> = { id: "1", password: "xxxx", } return c.json(responseUser, 200) }, )

z.infer<typeof UserResponseSchema>という型で検証するようにしました。
これによってpasswordの部分で型エラーが出ます。

error TS2353: Object literal may only specify known properties, and 'password' does not exist in type '{ id: string; }'. 80 password: "xxxx", ~~~~~~~~

しかし、Typescriptは子要素のfieldで型エラーが出ないケースが存在し、実用的でありませんでした。

const a = { a: 0, b: { a: 0, b: 1 } } const c = (d: { a: number; b: { a: number } }) => {} c(a) // 型エラーにならない c({ a: 0, b: { a: 0, b: 1 } }) // 型エラーになる

詳しくはこの辺の記事に書かれている

入れ子になったfieldsやmapメソッドや関数を使って代入したときに型エラーが出なくなってしまう。

yosi

Noh を作ってるエンジニア。

目次