[!NOTE]
Repo is archived due to not having maintainers - feel free to contact us if you want to take over maintenance.
Otherwise, we might reintroduce trpc-openapi to the world as a paid package.
For now, look at https://www.npmjs.com/package/trpc-to-openapi for an alternative
trpc-openapi
is maintained by ProsePilot - simple, fast and free online writing tools.1. Install trpc-openapi
.
# npm
npm install trpc-openapi
# yarn
yarn add trpc-openapi
2. Add OpenApiMeta
to your tRPC instance.
import { initTRPC } from '@trpc/server';
import { OpenApiMeta } from 'trpc-openapi';
const t = initTRPC.meta<OpenApiMeta>().create(); /* π */
3. Enable openapi
support for a procedure.
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}!` };
});
});
4. Generate an OpenAPI document.
import { generateOpenApiDocument } from 'trpc-openapi';
import { appRouter } from '../appRouter';
/* π */
export const openApiDocument = generateOpenApiDocument(appRouter, {
title: 'tRPC OpenAPI',
version: '1.0.0',
baseUrl: 'http://localhost:3000',
});
5. Add an trpc-openapi
handler to your app.
We currently support adapters for Express
, Next.js
, Serverless
, Fastify
, Nuxt
& Node:HTTP
.
Fetch
, Cloudflare Workers
& more soonβ’, PRs are welcomed π.
import http from 'http';
import { createOpenApiHttpHandler } from 'trpc-openapi';
import { appRouter } from '../appRouter';
const server = http.createServer(createOpenApiHttpHandler({ router: appRouter })); /* π */
server.listen(3000);
6. Profit π€
// client.ts
const res = await fetch('http://localhost:3000/say-hello?name=James', { method: 'GET' });
const body = await res.json(); /* { greeting: 'Hello James!' } */
Peer dependencies:
tRPC
Server v10 (@trpc/server
) must be installed.Zod
v3 (zod@^3.14.4
) must be installed (recommended ^3.20.0
).For a procedure to support OpenAPI the following must be true:
input
and output
parsers are present AND use Zod
validation.input
parsers extend Object<{ [string]: String | Number | BigInt | Date }>
or Void
.input
parsers extend Object<{ [string]: AnyType }>
or Void
.meta.openapi.method
is GET
, POST
, PATCH
, PUT
or DELETE
.meta.openapi.path
is a string starting with /
.meta.openapi.path
parameters exist in input
parser as String | Number | BigInt | Date
Please note:
transformers
(such as superjson
) are ignored.Procedures with a GET
/DELETE
method will accept inputs via URL query parameters
. Procedures with a POST
/PATCH
/PUT
method will accept inputs via the request body
with a application/json
or application/x-www-form-urlencoded
content type.
A procedure can accept a set of inputs via URL path parameters. You can add a path parameter to any OpenAPI procedure by using curly brackets around an input name as a path segment in the meta.openapi.path
field.
Query & path parameter inputs are always accepted as a string
. This library will attempt to coerce your input values to the following primitive types out of the box: number
, boolean
, bigint
and date
. If you wish to support others such as object
, array
etc. please use 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/James?greeting=Hello' /* π */, {
method: 'GET',
});
const body = await res.json(); /* { greeting: 'Hello James!' } */
// 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/James' /* π */, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ greeting: 'Hello' }),
});
const body = await res.json(); /* { greeting: 'Hello James!' } */
Any custom headers can be specified in the meta.openapi.headers
array, these headers will not be validated on request. Please consider using Authorization for first-class OpenAPI auth/security support.
Status codes will be 200
by default for any successful requests. In the case of an error, the status code will be derived from the thrown TRPCError
or fallback to 500
.
You can modify the status code or headers for any response using the responseMeta
function.
Please see error status codes here.
To create protected endpoints, add protect: true
to the meta.openapi
object of each tRPC procedure. By default, you can then authenticate each request with the createContext
function using the Authorization
header with the Bearer
scheme. If you wish to authenticate requests using a different/additional methods (such as custom headers, or cookies) this can be overwritten by specifying securitySchemes
object.
Explore a complete example here.
import { TRPCError, initTRPC } from '@trpc/server';
import { OpenApiMeta } from 'trpc-openapi';
type User = { id: string; name: string };
const users: User[] = [
{
id: 'usr_123',
name: 'James',
},
];
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 James!' } */
For advanced use-cases, please find examples in our complete test suite.
Please see full example here.
import { createExpressMiddleware } from '@trpc/server/adapters/express';
import express from 'express';
import { createOpenApiExpressMiddleware } from 'trpc-openapi';
import { appRouter } from '../appRouter';
const app = express();
app.use('/api/trpc', createExpressMiddleware({ router: appRouter }));
app.use('/api', createOpenApiExpressMiddleware({ router: appRouter })); /* π */
app.listen(3000);
Please see full example here.
// pages/api/[...trpc].ts
import { createOpenApiNextHandler } from 'trpc-openapi';
import { appRouter } from '../../server/appRouter';
export default createOpenApiNextHandler({ router: appRouter });
Please see full example here.
import { createOpenApiAwsLambdaHandler } from 'trpc-openapi';
import { appRouter } from './appRouter';
export const openApi = createOpenApiAwsLambdaHandler({ router: appRouter });
Please see full example here.
import { fastifyTRPCPlugin } from '@trpc/server/adapters/fastify';
import Fastify from 'fastify';
import { fastifyTRPCOpenApiPlugin } from 'trpc-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();
Please see full typings here.
Property | Type | Description | Required |
---|---|---|---|
title |
string |
The title of the API. | true |
description |
string |
A short description of the API. | false |
version |
string |
The version of the OpenAPI document. | true |
baseUrl |
string |
The base URL of the target server. | true |
docsUrl |
string |
A URL to any external documentation. | false |
tags |
string[] |
A list for ordering endpoint groups. | false |
securitySchemes |
Record<string, SecuritySchemeObject> |
Defaults to Authorization header with Bearer scheme |
false |
Please see full typings here.
Property | Type | Description | Required | Default |
---|---|---|---|---|
enabled |
boolean |
Exposes this procedure to trpc-openapi adapters and on the OpenAPI document. |
false |
true |
method |
HttpMethod |
HTTP method this endpoint is exposed on. Value can be GET , POST , PATCH , PUT or DELETE . |
true |
undefined |
path |
string |
Pathname this endpoint is exposed on. Value must start with / , specify path parameters using {} . |
true |
undefined |
protect |
boolean |
Requires this endpoint to use a security scheme. | false |
false |
summary |
string |
A short summary of the endpoint included in the OpenAPI document. | false |
undefined |
description |
string |
A verbose description of the endpoint included in the OpenAPI document. | false |
undefined |
tags |
string[] |
A list of tags used for logical grouping of endpoints in the OpenAPI document. | false |
undefined |
headers |
ParameterObject[] |
An array of custom headers to add for this endpoint in the OpenAPI document. | false |
undefined |
contentTypes |
ContentType[] |
A set of content types specified as accepted in the OpenAPI document. | false |
['application/json'] |
deprecated |
boolean |
Whether or not to mark an endpoint as deprecated | false |
false |
Please see full typings here.
Property | Type | Description | Required |
---|---|---|---|
router |
Router |
Your application tRPC router. | true |
createContext |
Function |
Passes contextual (ctx ) data to procedure resolvers. |
false |
responseMeta |
Function |
Returns any modifications to statusCode & headers. | false |
onError |
Function |
Called if error occurs inside handler. | false |
maxBodySize |
number |
Maximum request body size in bytes (default: 100kb). | false |
Still using tRPC v9? See our .interop()
example.
Distributed under the MIT License. See LICENSE for more information.
James Berry - Follow me on Twitter @jlalmes π