Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

trpc-to-openapi

Package Overview
Dependencies
Maintainers
0
Versions
11
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

trpc-to-openapi

tRPC OpenAPI

  • 2.1.0
  • latest
  • Source
  • npm
  • Socket score

Version published
Maintainers
0
Created
Source

trpc-to-openapi



OpenAPI support for tRPC 🧩

  • tRPC ^11.0.0-rc.648 only 👈
  • Easy REST endpoints for your tRPC procedures.
  • Perfect for incremental adoption.
  • Supports all OpenAPI versions.

Note: This project is a fork of a fork, with full credit to the original authors. It appears that the original author has abandoned the project, so I plan to add new features in the near future.

Changelog

  • v2.1.0

    • Updated the minimum version of zod-openapi to 4.1.0.
    • Changed zod-openapi to a peer dependency.
    • The protect option now defaults to true.
    • Improved Error schema titles
  • v2.0.4

    • Upgraded to tRPC 11.0.0-rc.648.
  • v2.0.3

    • Added support for array inputs in GET requests.

Usage

1. Install trpc-to-openapi.

# npm
npm install trpc-to-openapi
# yarn
yarn add trpc-to-openapi

2. Add OpenApiMeta to your tRPC instance.

import { initTRPC } from '@trpc/server';
import { OpenApiMeta } from 'trpc-to-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-to-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-to-openapi handler to your app.

We currently support adapters for Express, Next.js, Fastify, Nuxt & Node:HTTP.

Fetch, Cloudflare Workers & more soon™, PRs are welcomed 🙌.

No support for 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. Profit 🤑

// client.ts
const res = await fetch('http://localhost:3000/say-hello?name=Lily', { method: 'GET' });
const body = await res.json(); /* { greeting: 'Hello Lily!' } */

Requirements

Peer dependencies:

  • tRPC Server v11 (@trpc/server) must be installed.
  • Zod v3 (zod@^3.23.8) must be installed.

For a procedure to support OpenAPI the following must be true:

  • Both input and output parsers are present AND use Zod validation.
  • Query input parsers extend Object<{ [string]: String | Number | BigInt | Date }> or Void.
  • Mutation 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:

  • Data transformers (such as superjson) are ignored.
  • Trailing slashes are ignored.
  • Routing is case-insensitive.

HTTP Requests

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 content type.

Path parameters

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 parameters

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/Lily?greeting=Hello' /* 👈 */, {
  method: 'GET',
});
const body = await res.json(); /* { greeting: 'Hello Lily!' } */

Request body

// 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!' } */

Custom headers

Any custom headers can be specified in the meta.openapi.requestHeaders and meta.openapi.responseHeaders zod object schema, these headers will not be validated. Please consider using Authorization for first-class OpenAPI auth/security support.

HTTP Responses

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.

Authorization

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.

Server
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}!` };
    }),
});
Client
const res = await fetch('http://localhost:3000/say-hello', {
  method: 'GET',
  headers: { Authorization: 'Bearer usr_123' } /* 👈 */,
});
const body = await res.json(); /* { greeting: 'Hello Lily!' } */

Examples

For advanced use-cases, please find examples in our complete test suite.

With Express

Please see full example here.

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);
With Next.js app router

Please see full example here.

// 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,
};
With Next.js pages router

Please see full example here.

// pages/api/[...trpc].ts
import { createOpenApiNextHandler } from 'trpc-to-openapi';

import { appRouter } from '../../server/appRouter';

export default createOpenApiNextHandler({ router: appRouter });
With Fastify

Please see full example here.

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();

Types

GenerateOpenApiDocumentOptions

Please see full typings here.

PropertyTypeDescriptionRequired
titlestringThe title of the API.true
descriptionstringA short description of the API.false
versionstringThe version of the OpenAPI document.true
baseUrlstringThe base URL of the target server.true
docsUrlstringA URL to any external documentation.false
tagsstring[]A list for ordering endpoint groups.false
securitySchemesRecord<string, SecuritySchemeObject>Defaults to Authorization header with Bearer schemefalse
OpenApiMeta

Please see full typings here.

PropertyTypeDescriptionRequiredDefault
enabledbooleanExposes this procedure to trpc-to-openapi adapters and on the OpenAPI document.falsetrue
methodHttpMethodHTTP method this endpoint is exposed on. Value can be GET, POST, PATCH, PUT or DELETE.trueundefined
pathstringPathname this endpoint is exposed on. Value must start with /, specify path parameters using {}.trueundefined
protectbooleanRequires this endpoint to use a security scheme.falsetrue
summarystringA short summary of the endpoint included in the OpenAPI document.falseundefined
descriptionstringA verbose description of the endpoint included in the OpenAPI document.falseundefined
tagsstring[]A list of tags used for logical grouping of endpoints in the OpenAPI document.falseundefined
requestHeadersAnyZodObjectA zod object schema describing any custom headers to add to the request for this endpoint in the OpenAPI document.falseundefined
responseHeadersAnyZodObjectA zod object schema describing any custom headers to add to the response for this endpoint in the OpenAPI document.falseundefined
successDescriptionstringA string to use as the description for a successful response.false'Successful response'
errorResponsesnumber[] | { [key: number]: string }A list of error response codes or an object of response codes and their description to add to the responses for this endpoint.falseundefined
contentTypesOpenApiContentType[]A set of content types specified as accepted in the OpenAPI document.false['application/json']
deprecatedbooleanWhether or not to mark an endpoint as deprecatedfalsefalse
CreateOpenApiNodeHttpHandlerOptions

Please see full typings here.

PropertyTypeDescriptionRequired
routerRouterYour application tRPC router.true
createContextFunctionPasses contextual (ctx) data to procedure resolvers.false
responseMetaFunctionReturns any modifications to statusCode & headers.false
onErrorFunctionCalled if error occurs inside handler.false
maxBodySizenumberMaximum request body size in bytes (default: 100kb).false

Still using tRPC v9? See our .interop() example.

License

Distributed under the MIT License. See LICENSE for more information.

Keywords

FAQs

Package last updated on 02 Dec 2024

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc