hanzo-trpc-openapi

@hanzo/trpc-openapi is a TypeScript library that generates OpenAPI 3.x documents from tRPC v11 routers and provides HTTP adapters to serve tRPC procedures as REST endpoints.

Hanzo tRPC OpenAPI - Generate OpenAPI Specs from tRPC Routers

Category: Hanzo Ecosystem Related Skills: hanzo/hanzo-platform.md, hanzo/hanzo-openapi.md

Overview

@hanzo/trpc-openapi is a TypeScript library that generates OpenAPI 3.x documents from tRPC v11 routers and provides HTTP adapters to serve tRPC procedures as REST endpoints. Published on npm as @hanzo/trpc-openapi. This is a maintained fork of the abandoned trpc-to-openapi project with Hanzo-specific enhancements.

Why @hanzo/trpc-openapi?

Tech Stack

OSS Base

Fork of trpc-to-openapi. Hanzo-specific additions:

Repo: github.com/hanzoai/trpc-openapi

When to use

Hard requirements

  1. @trpc/server ^11.1.0
  2. zod ^4.0.0 (Zod v4, not v3)
  3. zod-openapi ^5.0.1
  4. Node.js 22+ (based on @types/node ^22)

Quick reference

| Item | Value | |------|-------| | npm Package | @hanzo/trpc-openapi | | Version | 0.0.17 | | Repo | github.com/hanzoai/trpc-openapi | | Branch | main | | License | MIT | | tRPC Version | ^11.1.0 (peer dep) | | Zod Version | ^4.0.0 (peer dep) | | Build | pnpm build (runs tests first) | | Test | pnpm test | | Exports | CJS (dist/cjs/) + ESM (dist/esm/) |

One-file quickstart

Install

pnpm add @hanzo/trpc-openapi
# peer deps
pnpm add @trpc/server zod zod-openapi

Define a tRPC router with OpenAPI metadata

import { initTRPC } from '@trpc/server';
import { z } from 'zod';
import { OpenApiMeta, generateOpenApiDocument, createOpenApiExpressMiddleware } from '@hanzo/trpc-openapi';
import express from 'express';

// 1. Create tRPC instance with OpenApiMeta
const t = initTRPC.meta<OpenApiMeta>().create();

// 2. Define router with openapi metadata
const appRouter = t.router({
 getUser: t.procedure
 .meta({ openapi: { method: 'GET', path: '/users/{id}' } })
 .input(z.object({ id: z.string() }))
 .output(z.object({ id: z.string(), name: z.string() }))
 .query(({ input }) => ({ id: input.id, name: 'Hanzo User' })),

 createUser: t.procedure
 .meta({ openapi: { method: 'POST', path: '/users', protect: true } })
 .input(z.object({ name: z.string(), email: z.string().email() }))
 .output(z.object({ id: z.string(), name: z.string() }))
 .mutation(({ input }) => ({ id: 'usr_new', name: input.name })),
});

// 3. Generate OpenAPI document
const openApiDoc = generateOpenApiDocument(appRouter, {
 title: 'My API',
 version: '1.0.0',
 baseUrl: 'http://localhost:3000',
});

// 4. Serve REST endpoints
const app = express();
app.get('/openapi.json', (req, res) => res.json(openApiDoc));
app.use('/api', createOpenApiExpressMiddleware({ router: appRouter }));
app.listen(3000);

Core Concepts

Architecture

┌──────────────────────┐
│ tRPC Router │
│ (procedures + meta) │
└──────┬───────────────┘
 │
 ┌────▼────────────────────┐
 │ generateOpenApiDocument │──── OpenAPI 3.x JSON
 └────┬────────────────────┘
 │
 ┌────▼────────────────────┐
 │ HTTP Adapter │──── REST endpoints
 │ (Express/Fastify/Next) │ GET /users/{id}
 └─────────────────────────┘ POST /users

Adapters

| Adapter | Import | Handler Factory | |---------|--------|-----------------| | Express | @hanzo/trpc-openapi | createOpenApiExpressMiddleware | | Fastify | @hanzo/trpc-openapi | fastifyTRPCOpenApiPlugin | | Next.js Pages | @hanzo/trpc-openapi | createOpenApiNextHandler | | Next.js App Router | @hanzo/trpc-openapi | createOpenApiFetchHandler | | Nuxt | @hanzo/trpc-openapi | createOpenApiNuxtHandler | | Koa | @hanzo/trpc-openapi | createOpenApiKoaMiddleware | | Fetch | @hanzo/trpc-openapi | createOpenApiFetchHandler | | Node HTTP | @hanzo/trpc-openapi | createOpenApiHttpHandler | | Standalone | @hanzo/trpc-openapi | createOpenApiHttpHandler (with http.createServer) |

OpenApiMeta Fields

| Field | Type | Default | Description | |-------|------|---------|-------------| | method | GET\|POST\|PATCH\|PUT\|DELETE | (from proc type) | HTTP method | | path | /${string} | (from proc path) | URL path with {param} placeholders | | enabled | boolean | true | Include in OpenAPI doc and adapters | | protect | boolean | true | Require Authorization header | | override | boolean | false | Use only provided openapi object (no defaults) | | additional | boolean | false | Merge provided openapi on top of defaults | | tags | string[] | -- | Grouping tags | | summary | string | -- | Short description | | description | string | -- | Verbose description | | deprecated | boolean | false | Mark as deprecated | | contentTypes | string[] | ['application/json'] | Accepted content types | | requestHeaders | ZodObject | -- | Custom request headers schema | | responseHeaders | ZodObject | -- | Custom response headers schema | | errorResponses | number[]\|Record<number,string> | -- | Error response codes |

Input Handling

Filtering Procedures

const doc = generateOpenApiDocument<{ isPublic: boolean }>(appRouter, {
 title: 'API',
 version: '1.0.0',
 baseUrl: 'http://localhost:3000',
 filter: ({ metadata }) => metadata.isPublic === true,
});

Directory structure

trpc-openapi/
├── src/
│ ├── index.ts # Re-exports everything
│ ├── types.ts # OpenApiMeta, OpenApiMethod, response types
│ ├── generator/
│ │ ├── index.ts # generateOpenApiDocument()
│ │ ├── paths.ts # OpenAPI path generation
│ │ └── schema.ts # Zod-to-OpenAPI schema conversion
│ ├── adapters/
│ │ ├── express.ts # Express middleware
│ │ ├── fastify.ts # Fastify plugin
│ │ ├── next.ts # Next.js handler (pages + app)
│ │ ├── nuxt.ts # Nuxt handler
│ │ ├── koa.ts # Koa middleware
│ │ ├── fetch.ts # Fetch API handler
│ │ ├── standalone.ts # Standalone Node HTTP
│ │ └── node-http/ # Core Node HTTP handler
│ └── utils/ # Internal utilities
├── test/
│ ├── generator.test.ts # 115KB of generator tests
│ └── adapters/ # Adapter-specific tests
├── examples/
│ ├── with-express/
│ ├── with-fastify/
│ ├── with-nextjs/
│ ├── with-nextjs-appdir/
│ └── with-nuxtjs/
├── dist/
│ ├── cjs/ # CommonJS build output
│ └── esm/ # ESM build output
├── package.json # @hanzo/trpc-openapi v0.0.17
├── tsconfig.json
└── jest.config.ts

Troubleshooting

| Issue | Cause | Solution | |-------|-------|----------| | zod is not a function | Using Zod v3 instead of v4 | Upgrade to zod ^4.0.0 | | Procedures missing from OpenAPI doc | enabled: false in meta | Remove or set enabled: true | | Query params not parsed | Complex types in GET input | Use z.preprocess() for objects/arrays in query params | | 404 on REST endpoint | Path mismatch | Ensure meta.openapi.path matches exactly, paths are case-insensitive | | Auth not working | protect: true but no createContext | Implement createContext to extract Authorization header | | Type error with meta | tRPC not configured with OpenApiMeta | Use initTRPC.meta<OpenApiMeta>().create() |

Related Skills


Last Updated: 2026-03-13 Category: Hanzo Ecosystem Related: trpc, openapi, swagger, rest, api Prerequisites: TypeScript, tRPC v11, Zod v4