Derived Responses

defineDerivedResponse(...) helps when you already have a useful base response and want a more specific version of it without copying the whole definition.

This is most helpful for shared error responses.

The problem it solves

Suppose you already have a generic not-found response:

const NotFoundErrorDefinition = defineResponse({
  name: "NotFoundError",
  statusCode: HttpStatusCode.NOT_FOUND,
  description: "Resource not found",
  body: z.object({
    message: z.string(),
  }),
});

That is reusable, but sometimes an operation needs to add a little more detail, such as which todo was missing.

Deriving a response

const TodoNotFoundErrorDefinition = defineDerivedResponse(
  NotFoundErrorDefinition,
  {
    name: "TodoNotFoundError",
    body: z.object({
      todoId: z.string(),
    }),
  }
);

The derived response keeps the base response's unspecified fields and merges the object body with the new fields.

In practice, the derived response still has:

  • the 404 status code
  • the original description unless you override it
  • the base body fields such as message
  • the added body field todoId

How merging works

For object headers and object bodies, Typeweaver shallow-merges the base and derived Zod object schemas. Fields from the derived schema override fields with the same key in the base schema. If the body is not a z.object(...) (for example a z.string() or z.array(...)), the derived body replaces the base body entirely instead of merging.

That means this:

const UnauthorizedErrorDefinition = defineResponse({
  name: "UnauthorizedError",
  statusCode: HttpStatusCode.UNAUTHORIZED,
  description: "Unauthorized",
  header: z.object({
    "WWW-Authenticate": z.string(),
  }),
  body: z.object({
    message: z.string(),
  }),
});

const TodoUnauthorizedErrorDefinition = defineDerivedResponse(
  UnauthorizedErrorDefinition,
  {
    name: "TodoUnauthorizedError",
    body: z.object({
      todoId: z.string(),
    }),
  }
);

produces a derived response that keeps the header schema and extends the body schema.

When to use it

Use a derived response when:

  • you already have a shared base response that is almost right
  • you want operation-specific detail without repeating the base contract
  • you want the relationship to stay obvious in the spec

When not to use it

Prefer plain shared responses when the exact same response can be reused as-is.

Prefer a fresh defineResponse(...) when the new response is meaningfully different and the inheritance would make the spec harder to read.

Clarity matters more than squeezing out a little more reuse.

Was this page helpful?