打开API 支持 tRPC 主题 🧩
- 对tRPC ^11.1.0的支持
- 您的 tRPC 程序的简单 REST 端点 .
- 适合逐步收养.
- 支持所有 OpenAPI 版本.
说明: 这个项目是分叉一分叉,完全归功于原作者. 看来原作者放弃了这个项目,所以我计划在不久的将来增加新的功能.
自定义( Dokploy- style)
此分叉增加了以下行为( 灵感来自 多克宝利:
- 默认需要可选输入和输出参数 – 在生成的OpenAPI文档中,可选输入/输出参数按需要处理.
.meta对所有程序都是可选的 - 无程序.meta({ openapi: ... })仍然以默认的 OpenAPI 元数据(从程序类型、从程序路径、,enabled: true, (中文(简体) ),protect: true) (中文(简体) ).- 两面旗
meta.openapi数字 :override- 当true,只提供openapi对象被使用(没有合并默认值).additional- 当true,默认值先应用,然后应用openapi数值在上方合并.
使用量
1. 安装 trpc-to-openapi。 。 。 .
# pnpm
pnpm add trpc-to-openapi
# npm
npm install trpc-to-openapi
# yarn
yarn add trpc-to-openapi
2. 添加 OpenApiMeta 给你的TRPC实例.
import { initTRPC } from '@trpc/server';
import { OpenApiMeta } from 'trpc-to-openapi';
const t = initTRPC.meta<OpenApiMeta>().create(); /* 👈 */
3. 启用 openapi 支持一个程序.
export const appRouter = t.router({
sayHello: t.procedure
.meta({ /* 👉 */ openapi: { method: 'GET', path: '/say-hello' } })
.input(z.object({ name: z.string() }))
.output(z.object({ greeting: z.string() }))
.query(({ input }) => {
return { greeting: `Hello ${input.name}!` };
}),
getStatus: t.procedure
.meta({ /* 👉 */ openapi: { method: 'GET', path: '/status' } })
.output(z.object({ status: z.string() }))
.query(() => {
return { status: 'healthy' };
});
});
4. 生成 OpenAPI 文档.
import { generateOpenApiDocument } from 'trpc-to-openapi';
import { appRouter } from '../appRouter';
import { UserSchema, ProductSchema } from '../schemas';
/* 👇 */
export const openApiDocument = generateOpenApiDocument(appRouter, {
title: 'tRPC OpenAPI',
version: '1.0.0',
baseUrl: 'http://localhost:3000',
defs: {
UserSchema,
ProductSchema,
},
});
5. 增加一个 trpc-to-openapi 操作员到你的应用程序.
我们目前支持适配器 Express, (中文(简体) ), Next.js, (中文(简体) ), Fastify, (中文(简体) ), Nuxt 打开 Node:HTTP。 。 。 .
Fetch, (中文(简体) ), Cloudflare Workers 更快的TM,PR受到欢迎.
没有支持 AWS lambdas
import http from 'http';
import { createOpenApiHttpHandler } from 'trpc-to-openapi';
import { appRouter } from '../appRouter';
const server = http.createServer(createOpenApiHttpHandler({ router: appRouter })); /* 👈 */
server.listen(3000);
6. 利润
// client.ts
const res = await fetch('http://localhost:3000/say-hello?name=OpenAPI', { method: 'GET' });
const body = await res.json(); /* { greeting: 'Hello OpenAPI!' } */
const statusRes = await fetch('http://localhost:3000/status', { method: 'GET' });
const statusBody = await statusRes.json(); /* { status: 'healthy' } */
所需资源
同伴依赖性 :
支持 OpenAPI 的程序如下: 必须 事实 :
- 一个
output解析器已存在并使用Zod审定. - 如果一个
input解析器存在, 它必须使用Zod审定. - 查询
input解析器( 当当前) 延伸Object<{ [string]: String | Number | BigInt | Date }>或者说Void。 。 。 . - 变异
input解析器( 当当前) 延伸Object<{ [string]: AnyType }>或者说Void。 。 。 . meta.openapi.method实值GET, (中文(简体) ),POST, (中文(简体) ),PATCH, (中文(简体) ),PUT或者说DELETE。 。 。 .meta.openapi.path是一个字符串,从/。 。 。 .meta.openapi.path参数(当存在时)存在于input解析为String | Number | BigInt | Date
请注意:
- 数据
transformers(例如:superjson)被忽略. - 被轻视了.
- 修行是无相相相.
HTTP 请求
程序a GET编号 :DELETE 方法将通过 URL 接受输入 query parametersA. 程序与 POST编号 :PATCH编号 :PUT 方法将通过 request body 带有 application/json 内容类型.
路径参数
一个程序可以通过URL路径参数接受一组输入. 您可以在 OpenAPI 程序中使用输入名称周围的圆括号作为路径段添加路径参数 meta.openapi.path 字段.
查询参数
查询路径参数输入总是被接受为 string。该图书馆将试图 胁迫 框中以下原始类型的输入值: number, (中文(简体) ), boolean, (中文(简体) ), bigint 财务报告和审定财务报表 date。如果您希望支持诸如 object, (中文(简体) ), array 请使用 z.preprocess()。 。 。 .
// Router
export const appRouter = t.router({
sayHello: t.procedure
.meta({ openapi: { method: 'GET', path: '/say-hello/{name}' /* 👈 */ } })
.input(z.object({ name: z.string() /* 👈 */, greeting: z.string() }))
.output(z.object({ greeting: z.string() }))
.query(({ input }) => {
return { greeting: `${input.greeting} ${input.name}!` };
});
});
// Client
const res = await fetch('http://localhost:3000/say-hello/Lily?greeting=Hello' /* 👈 */, {
method: 'GET',
});
const body = await res.json(); /* { greeting: 'Hello Lily!' } */
请求机构
// Router
export const appRouter = t.router({
sayHello: t.procedure
.meta({ openapi: { method: 'POST', path: '/say-hello/{name}' /* 👈 */ } })
.input(z.object({ name: z.string() /* 👈 */, greeting: z.string() }))
.output(z.object({ greeting: z.string() }))
.mutation(({ input }) => {
return { greeting: `${input.greeting} ${input.name}!` };
});
});
// Client
const res = await fetch('http://localhost:3000/say-hello/Lily' /* 👈 */, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ greeting: 'Hello' }),
});
const body = await res.json(); /* { greeting: 'Hello Lily!' } */
自定义信头
任何自定义标题都可以在 meta.openapi.requestHeaders 财务报告和审定财务报表 meta.openapi.responseHeaders zod 对象计划, 这些标题将不会被验证 。 请考虑使用 授权 用于一等的 OpenAPI 认证/安全支持.
HTTP 回复
状态代码将 200 以默认方式处理任何成功的请求。 如果出现错误,则状态代码将来自抛出 TRPCError 或退到 500。 。 。 .
您可以使用 responseMeta 函数.
请看 此处错误状态代码。 。 。 .
授权
要创建受保护的终点,请添加 protect: true 页:1 meta.openapi 每个 tRPC 程序的对象。 默认情况下,您可以使用 createContext 函数使用 Authorization 带有 Bearer 计划。 如果您想要使用不同的/ 附加方法( 如自定义信头或 cookie) 认证请求, 您可以通过指定来覆盖 securitySchemes 对象.
探索a 此处完整示例。 。 。 .
服务器
import { TRPCError, initTRPC } from '@trpc/server';
import { OpenApiMeta } from 'trpc-to-openapi';
type User = { id: string; name: string };
const users: User[] = [
{
id: 'usr_123',
name: 'Lily',
},
];
export type Context = { user: User | null };
export const createContext = async ({ req, res }): Promise<Context> => {
let user: User | null = null;
if (req.headers.authorization) {
const userId = req.headers.authorization.split(' ')[1];
user = users.find((_user) => _user.id === userId);
}
return { user };
};
const t = initTRPC.context<Context>().meta<OpenApiMeta>().create();
export const appRouter = t.router({
sayHello: t.procedure
.meta({ openapi: { method: 'GET', path: '/say-hello', protect: true /* 👈 */ } })
.input(z.void()) // no input expected
.output(z.object({ greeting: z.string() }))
.query(({ input, ctx }) => {
if (!ctx.user) {
throw new TRPCError({ message: 'User not found', code: 'UNAUTHORIZED' });
}
return { greeting: `Hello ${ctx.user.name}!` };
}),
});
客户端
const res = await fetch('http://localhost:3000/say-hello', {
method: 'GET',
headers: { Authorization: 'Bearer usr_123' } /* 👈 */,
});
const body = await res.json(); /* { greeting: 'Hello Lily!' } */
实例
关于先进的使用案例,请在我们的 完整的测试套件。 。 。 .
有快车
请看 此处完整示例。 。 。 .
import { createExpressMiddleware } from '@trpc/server/adapters/express';
import express from 'express';
import { createOpenApiExpressMiddleware } from 'trpc-to-openapi';
import { appRouter } from '../appRouter';
const app = express();
app.use('/api/trpc', createExpressMiddleware({ router: appRouter }));
app.use('/api', createOpenApiExpressMiddleware({ router: appRouter })); /* 👈 */
app.listen(3000);
用 Next.js 应用程序路由器
请看 此处完整示例。 。 。 .
// src/app/[...trpc]/route.ts
import { appRouter } from '~/server/api/root';
import { createContext } from '~/server/api/trpc';
import { type NextRequest } from 'next/server';
import { createOpenApiFetchHandler } from 'trpc-to-openapi';
export const dynamic = 'force-dynamic';
const handler = (req: NextRequest) => {
// Handle incoming OpenAPI requests
return createOpenApiFetchHandler({
endpoint: '/',
router: appRouter,
createContext: () => createContext(req),
req,
});
};
export {
handler as GET,
handler as POST,
handler as PUT,
handler as PATCH,
handler as DELETE,
handler as OPTIONS,
handler as HEAD,
};
使用 Next.js 页面路由器
请看 此处完整示例。 。 。 .
// pages/api/[...trpc].ts
import { createOpenApiNextHandler } from 'trpc-to-openapi';
import { appRouter } from '../../server/appRouter';
export default createOpenApiNextHandler({ router: appRouter });
使用快捷键
请看 此处完整示例。 。 。 .
import { fastifyTRPCPlugin } from '@trpc/server/adapters/fastify';
import Fastify from 'fastify';
import { fastifyTRPCOpenApiPlugin } from 'trpc-to-openapi';
import { appRouter } from './router';
const fastify = Fastify();
async function main() {
await fastify.register(fastifyTRPCPlugin, { router: appRouter });
await fastify.register(fastifyTRPCOpenApiPlugin, { router: appRouter }); /* 👈 */
await fastify.listen({ port: 3000 });
}
main();
类型
生成 OpenApi 文档选项
请看 这里有完整的打字。 。 。 .
| 财产 | 类型 | 说明 | 需求 |
|---|---|---|---|
title | string | API名称. | true |
description | string | API简介. | false |
version | string | OpenAPI文档的版本. | true |
baseUrl | string | 目标服务器的基础 URL . | true |
docsUrl | string | 外部文档的 URL . | false |
tags | string[] | 命令端点组的列表 . | false |
securitySchemes | Record<string, SecuritySchemeObject> | 默认为 Authorization 标题带有 Bearer 计划 | false |
filter | (ctx: { metadata: { openapi: NonNullable<OpenApiMeta['openapi']> } & TMeta }) => boolean | 可选过滤功能包含/排除生成的 OpenAPI 文档中的程序 . | false |
打开 ApiMeta
请看 这里有完整的打字。 。 。 .
| 财产 | 类型 | 说明 | 需求 | 默认 |
|---|---|---|---|---|
enabled | boolean | 将这一程序曝光到 trpc-to-openapi 适配器和 OpenAPI 文档. | false | true |
method | HttpMethod | HTTP方法 此端点被曝光 。 值可以是 GET, (中文(简体) ), POST, (中文(简体) ), PATCH, (中文(简体) ), PUT 或者说 DELETE。 。 。 . | true | undefined |
path | string | 此端点已打开路径名 。 数值必须从 /,使用 {}。 。 。 . | true | undefined |
protect | boolean | 要求该端点使用安全计划. | false | true |
summary | string | OpenAPI文档中包含的终点的简短摘要. | false | undefined |
description | string | OpenAPI文档中包含的终点的动词描述. | false | undefined |
tags | string[] | OpenAPI 文档中用于逻辑组合端点的标记列表. | false | undefined |
requestHeaders | AnyZodObject | 一个 Zod 对象计划描述任何自定义头来添加到 OpenAPI 文档中此端点的请求. | false | undefined |
responseHeaders | AnyZodObject | 一个 Zod 对象计划描述任何自定义头来添加到OpenAPI文档中这个端点的响应. | false | undefined |
successDescription | string | 用于描述成功响应的字符串 . | false | 'Successful response' |
errorResponses | number[] | { [key: number]: string } | 列出错误响应代码或响应代码的对象及其描述,以加入这个端点的响应. | false | undefined |
contentTypes | OpenApiContentType[] | OpenAPI 文档中指定的一组内容类型. | false | ['application/json'] |
deprecated | boolean | 是否将一个终点标为已贬值 | false | false |
创建 OpenApi 节点HttpHandler 选项
请看 这里有完整的打字。 。 。 .
| 财产 | 类型 | 说明 | 需求 |
|---|---|---|---|
router | Router | 您的应用程序 tRPC 路由器 . | true |
createContext | Function | 传递背景( E)ctx向程序解决者提供数据. | false |
responseMeta | Function | 返回对状态代码和标题的任何修改. | false |
onError | Function | 处理器内出错时调用 . | false |
maxBodySize | number | 以字节表示的最大请求体积( 默认: 100kb) . | false |
许可证
根据麻省理工学院的许可证分发。 更多信息见LICENSE.
在 OpenAPI 输出中过滤程序
你可以用这个 filter 选项,从生成的 OpenAPI 文档中选择性地包含或排除程序。 过滤器函数接收带有程序元数据的上下文对象. 返回时 true 包括程序,或 false 将它排除在外.
示例
const appRouter = t.router({
publicProc: t.procedure
.meta({ openapi: { method: 'GET', path: '/public' }, isPublic: true })
.input(z.object({}))
.output(z.object({ result: z.string() }))
.query(() => ({ result: 'public' })),
privateProc: t.procedure
.meta({ openapi: { method: 'GET', path: '/private' }, isPublic: false })
.input(z.object({}))
.output(z.object({ result: z.string() }))
.query(() => ({ result: 'private' })),
});
// Only include procedures where isPublic is true
const openApiDocument = generateOpenApiDocument(appRouter, {
...defaultDocOpts,
filter: ({ metadata }) => metadata.isPublic === true,
});
// openApiDocument.paths will only include '/public'
也可以通过一个元数据通用到 generateOpenApiDocument 获取过滤器函数中自定义元数据的适当类型:
// Define your custom metadata type
interface MyMeta {
isPublic: boolean;
}
const openApiDocument = generateOpenApiDocument<MyMeta>(appRouter, {
...defaultDocOpts,
filter: ({ metadata }) => metadata.isPublic === true,
});