Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/jlalmes/trpc-openapi
OpenAPI support for tRPC π§©
https://github.com/jlalmes/trpc-openapi
nodejs openapi rest swagger trpc typescript
Last synced: 26 days ago
JSON representation
OpenAPI support for tRPC π§©
- Host: GitHub
- URL: https://github.com/jlalmes/trpc-openapi
- Owner: jlalmes
- License: mit
- Created: 2022-05-04T12:33:51.000Z (over 2 years ago)
- Default Branch: master
- Last Pushed: 2024-04-14T03:33:38.000Z (7 months ago)
- Last Synced: 2024-05-01T15:59:15.436Z (6 months ago)
- Topics: nodejs, openapi, rest, swagger, trpc, typescript
- Language: TypeScript
- Homepage: https://www.npmjs.com/package/trpc-openapi
- Size: 5.02 MB
- Stars: 2,006
- Watchers: 9
- Forks: 124
- Open Issues: 91
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
- awesome - jlalmes/trpc-openapi - OpenAPI support for tRPC 𧩠(TypeScript)
- awesome-javascript - trpc-openapi
- awesome-javascript - trpc-openapi
README
![trpc-openapi](assets/trpc-openapi-readme.png)
#### `trpc-openapi` is maintained by ProsePilot - simple, fast and free online [writing tools](https://www.prosepilot.com/tools).
---
## **[OpenAPI](https://swagger.io/specification/) support for [tRPC](https://trpc.io/)** π§©
- Easy REST endpoints for your tRPC procedures.
- Perfect for incremental adoption.
- OpenAPI version 3.0.3.## Usage
**1. Install `trpc-openapi`.**
```bash
# npm
npm install trpc-openapi
# yarn
yarn add trpc-openapi
```**2. Add `OpenApiMeta` to your tRPC instance.**
```typescript
import { initTRPC } from '@trpc/server';
import { OpenApiMeta } from 'trpc-openapi';const t = initTRPC.meta().create(); /* π */
```**3. Enable `openapi` support for a procedure.**
```typescript
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.**
```typescript
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`](http://expressjs.com/), [`Next.js`](https://nextjs.org/), [`Serverless`](https://www.serverless.com/), [`Fastify`](https://www.fastify.io/), [`Nuxt`](https://nuxtjs.org/) & [`Node:HTTP`](https://nodejs.org/api/http.html).
[`Fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API), [`Cloudflare Workers`](https://workers.cloudflare.com/) & more soonβ’, PRs are welcomed π.
```typescript
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 π€**
```typescript
// client.ts
const res = await fetch('http://localhost:3000/say-hello?name=James', { method: 'GET' });
const body = await res.json(); /* { greeting: 'Hello James!' } */
```## Requirements
Peer dependencies:
- [`tRPC`](https://github.com/trpc/trpc) Server v10 (`@trpc/server`) must be installed.
- [`Zod`](https://github.com/colinhacks/zod) v3 (`zod@^3.14.4`) must be installed (recommended `^3.20.0`).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`](https://trpc.io/docs/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` or `application/x-www-form-urlencoded` 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](https://github.com/colinhacks/zod#coercion-for-primitives) 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()`](https://github.com/colinhacks/zod#preprocess).
```typescript
// 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!' } */
```### Request body
```typescript
// 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!' } */
```### Custom headers
Any custom headers can be specified in the `meta.openapi.headers` array, these headers will not be validated on request. Please consider using [Authorization](#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](src/adapters/node-http/errors.ts).
## 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](examples/with-nextjs/src/server/router.ts).
#### Server
```typescript
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 => {
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().meta().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
```typescript
const res = await fetch('http://localhost:3000/say-hello', {
method: 'GET',
headers: { Authorization: 'Bearer usr_123' } /* π */,
});
const body = await res.json(); /* { greeting: 'Hello James!' } */
```## Examples
_For advanced use-cases, please find examples in our [complete test suite](test)._
#### With Express
Please see [full example here](examples/with-express).
```typescript
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);
```#### With Next.js
Please see [full example here](examples/with-nextjs).
```typescript
// pages/api/[...trpc].ts
import { createOpenApiNextHandler } from 'trpc-openapi';import { appRouter } from '../../server/appRouter';
export default createOpenApiNextHandler({ router: appRouter });
```#### With AWS Lambda
Please see [full example here](examples/with-serverless).
```typescript
import { createOpenApiAwsLambdaHandler } from 'trpc-openapi';import { appRouter } from './appRouter';
export const openApi = createOpenApiAwsLambdaHandler({ router: appRouter });
```#### With Fastify
Please see [full example here](examples/with-fastify).
```typescript
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();
```## Types
#### GenerateOpenApiDocumentOptions
Please see [full typings here](src/generator/index.ts).
| 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` | Defaults to `Authorization` header with `Bearer` scheme | `false` |#### OpenApiMeta
Please see [full typings here](src/types.ts).
| 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` |#### CreateOpenApiNodeHttpHandlerOptions
Please see [full typings here](src/adapters/node-http/core.ts).
| 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()`](examples/with-interop) example._
## License
Distributed under the MIT License. See LICENSE for more information.
## Contact
James Berry - Follow me on Twitter [@jlalmes](https://twitter.com/jlalmes) π