Skip to content
This repository was archived by the owner on Nov 19, 2024. It is now read-only.

Commit 6ab276a

Browse files
authored
v1.0.0 (#143)
1 parent 506f1b9 commit 6ab276a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+30662
-11698
lines changed

.eslintrc.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// @ts-check
2+
3+
/** @type {import('eslint').Linter.Config} */
14
module.exports = {
25
root: true,
36
extends: [
@@ -21,9 +24,12 @@ module.exports = {
2124
node: true,
2225
},
2326
rules: {
27+
'@typescript-eslint/no-misused-promises': ['error', { checksVoidReturn: false }],
2428
'@typescript-eslint/no-unsafe-argument': 'warn',
2529
'@typescript-eslint/no-unsafe-assignment': 'warn',
30+
'@typescript-eslint/no-unsafe-call': 'warn',
2631
'@typescript-eslint/no-unsafe-member-access': 'warn',
32+
'@typescript-eslint/no-unsafe-return': 'warn',
2733
},
2834
overrides: [
2935
{

.github/workflows/test.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: Test
22
on: [push]
3-
jobs:
3+
jobs:
44
test:
55
runs-on: ubuntu-latest
66
steps:
@@ -9,11 +9,11 @@ jobs:
99

1010
- name: Setup node
1111
uses: actions/setup-node@v3
12-
with:
12+
with:
1313
node-version: 16
1414

1515
- name: Install dependencies
16-
run: npm ci
16+
run: npm ci --force
1717

1818
- name: Run tests
1919
run: npm test

README.md

Lines changed: 67 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -21,37 +21,35 @@
2121

2222
```bash
2323
# npm
24-
npm install trpc-openapi
24+
npm install trpc-openapi@alpha
2525
# yarn
26-
yarn add trpc-openapi
26+
yarn add trpc-openapi@alpha
2727
```
2828

29-
**2. Add `OpenApiMeta` to your tRPC router.**
29+
**2. Add `OpenApiMeta` to your tRPC instance.**
3030

3131
```typescript
32-
import * as trpc from '@trpc/server';
32+
import { initTRPC } from '@trpc/server';
3333
import { OpenApiMeta } from 'trpc-openapi';
3434

35-
export const appRouter = trpc.router<any, OpenApiMeta /* 👈 */>();
35+
const t = initTRPC.meta<OpenApiMeta>().create(); /* 👈 */
3636
```
3737

3838
**3. Enable `openapi` support for a procedure.**
3939

4040
```typescript
41-
import * as trpc from '@trpc/server';
42-
import { OpenApiMeta } from 'trpc-openapi';
43-
44-
export const appRouter = trpc.router<any, OpenApiMeta>().query('sayHello', {
45-
meta: { /* 👉 */ openapi: { enabled: true, method: 'GET', path: '/say-hello' } },
46-
input: z.object({ name: z.string() }),
47-
output: z.object({ greeting: z.string() }),
48-
resolve: ({ input }) => {
49-
return { greeting: `Hello ${input.name}!` };
50-
},
41+
export const appRouter = t.router({
42+
sayHello: t.procedure
43+
.meta({ /* 👉 */ openapi: { method: 'GET', path: '/say-hello' } })
44+
.input(z.object({ name: z.string() }))
45+
.output(z.object({ greeting: z.string() }))
46+
.query(({ input }) => {
47+
return { greeting: `Hello ${input.name}!` };
48+
});
5149
});
5250
```
5351

54-
**4. Generate OpenAPI v3 document.**
52+
**4. Generate an OpenAPI document.**
5553

5654
```typescript
5755
import { generateOpenApiDocument } from 'trpc-openapi';
@@ -68,9 +66,9 @@ export const openApiDocument = generateOpenApiDocument(appRouter, {
6866

6967
**5. Add an `trpc-openapi` handler to your app.**
7068

71-
We currently support adapters for [`Express`](http://expressjs.com/), [`Next.js`](https://nextjs.org/) & [`node:http`](https://nodejs.org/api/http.html).
69+
We currently support adapters for [`Express`](http://expressjs.com/), [`Next.js`](https://nextjs.org/), [`Serverless`](https://www.serverless.com/) & [`node:http`](https://nodejs.org/api/http.html).
7270

73-
[`Fastify`](https://www.fastify.io/) & [`Serverless`](https://www.serverless.com/) soon™, PRs are welcomed 🙌.
71+
[`Fastify`](https://www.fastify.io/) & more soon™, PRs are welcomed 🙌.
7472

7573
```typescript
7674
import http from 'http';
@@ -88,29 +86,29 @@ server.listen(3000);
8886
```typescript
8987
// client.ts
9088
const res = await fetch('http://localhost:3000/say-hello?name=James', { method: 'GET' });
91-
const body = await res.json(); /* { ok: true, data: { greeting: 'Hello James!' } } */
89+
const body = await res.json(); /* { greeting: 'Hello James!' } */
9290
```
9391

9492
## Requirements
9593

9694
Peer dependencies:
9795

98-
- [`tRPC`](https://github.com/trpc/trpc) Server v9 (`@trpc/server@^9.23.0`) must be installed.
96+
- [`tRPC`](https://github.com/trpc/trpc) Server v10 (`@trpc/server@next`) must be installed.
9997
- [`Zod`](https://github.com/colinhacks/zod) v3 (`zod@^3.14.4`) must be installed.
10098

10199
For a procedure to support OpenAPI the following _must_ be true:
102100

103101
- Both `input` and `output` parsers are present AND use `Zod` validation.
104102
- Query `input` parsers extend `ZodObject<{ [string]: ZodString }>` or `ZodVoid`.
105103
- Mutation `input` parsers extend `ZodObject<{ [string]: ZodAnyType }>` or `ZodVoid`.
106-
- `meta.openapi.enabled` is set to `true`.
107104
- `meta.openapi.method` is `GET`, `POST`, `PATCH`, `PUT` or `DELETE`.
108105
- `meta.openapi.path` is a string starting with `/`.
109106
- `meta.openapi.path` parameters exist in `input` parser as `ZodString`
110107

111108
Please note:
112109

113110
- Data [`transformers`](https://trpc.io/docs/data-transformers) are ignored.
111+
- tRPC v9 `.interop()` routers are not supported.
114112
- Trailing slashes are ignored.
115113
- Routing is case-insensitive.
116114

@@ -120,41 +118,43 @@ Procedures with a `GET`/`DELETE` method will accept inputs via URL `query parame
120118

121119
### Path parameters
122120

123-
A procedure can accept a set of inputs via URL path parameters. You can add a path parameter to any OpenAPI enabled procedure by using curly brackets around an input name as a path segment in the `meta.openapi.path` field.
121+
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.
124122

125123
### Query parameters
126124

127125
Query & path parameter inputs are always accepted as a `string`, if you wish to support other primitives such as `number`, `boolean`, `Date` etc. please use [`z.preprocess()`](https://github.com/colinhacks/zod#preprocess).
128126

129127
```typescript
130128
// Router
131-
export const appRouter = trpc.router<Context, OpenApiMeta>().query('sayHello', {
132-
meta: { openapi: { enabled: true, method: 'GET', path: '/say-hello/{name}' /* 👈 */ } },
133-
input: z.object({ name: z.string() /* 👈 */, greeting: z.string() }),
134-
output: z.object({ greeting: z.string() }),
135-
resolve: ({ input }) => {
136-
return { greeting: `${input.greeting} ${input.name}!` };
137-
},
129+
export const appRouter = t.router({
130+
sayHello: t.procedure
131+
.meta({ openapi: { method: 'GET', path: '/say-hello/{name}' /* 👈 */ } })
132+
.input(z.object({ name: z.string() /* 👈 */, greeting: z.string() }))
133+
.output(z.object({ greeting: z.string() }))
134+
.query(({ input }) => {
135+
return { greeting: `${input.greeting} ${input.name}!` };
136+
});
138137
});
139138

140139
// Client
141140
const res = await fetch('http://localhost:3000/say-hello/James?greeting=Hello' /* 👈 */, {
142141
method: 'GET',
143142
});
144-
const body = await res.json(); /* { ok: true, data: { greeting: 'Hello James!' } } */
143+
const body = await res.json(); /* { greeting: 'Hello James!' } */
145144
```
146145

147146
### Request body
148147

149148
```typescript
150149
// Router
151-
export const appRouter = trpc.router<Context, OpenApiMeta>().mutation('sayHello', {
152-
meta: { openapi: { enabled: true, method: 'POST', path: '/say-hello/{name}' /* 👈 */ } },
153-
input: z.object({ name: z.string() /* 👈 */, greeting: z.string() }),
154-
output: z.object({ greeting: z.string() }),
155-
resolve: ({ input }) => {
156-
return { greeting: `${input.greeting} ${input.name}!` };
157-
},
150+
export const appRouter = t.router({
151+
sayHello: t.procedure
152+
.meta({ openapi: { method: 'POST', path: '/say-hello/{name}' /* 👈 */ } })
153+
.input(z.object({ name: z.string() /* 👈 */, greeting: z.string() }))
154+
.output(z.object({ greeting: z.string() }))
155+
.mutation(({ input }) => {
156+
return { greeting: `${input.greeting} ${input.name}!` };
157+
});
158158
});
159159

160160
// Client
@@ -163,7 +163,7 @@ const res = await fetch('http://localhost:3000/say-hello/James' /* 👈 */, {
163163
headers: { 'Content-Type': 'application/json' },
164164
body: JSON.stringify({ greeting: 'Hello' }),
165165
});
166-
const body = await res.json(); /* { ok: true, data: { greeting: 'Hello James!' } } */
166+
const body = await res.json(); /* { greeting: 'Hello James!' } */
167167
```
168168

169169
### Custom headers
@@ -172,32 +172,12 @@ Any custom headers can be specified in the `meta.openapi.headers` array, these h
172172

173173
## HTTP Responses
174174

175-
Inspired by [Slack Web API](https://api.slack.com/web).
176-
177175
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`.
178176

179177
You can modify the status code or headers for any response using the `responseMeta` function.
180178

181179
Please see [error status codes here](src/adapters/node-http/errors.ts).
182180

183-
```jsonc
184-
{
185-
"ok": true,
186-
"data": "This is good" /* Output from tRPC procedure */
187-
}
188-
```
189-
190-
```jsonc
191-
{
192-
"ok": false,
193-
"error": {
194-
"message": "This is bad", /* Message from TRPCError */,
195-
"code": "BAD_REQUEST", /* Code from TRPCError */
196-
"issues": [...] /* (optional) ZodIssues[] from TRPCError */
197-
}
198-
}
199-
```
200-
201181
## Authorization
202182

203183
To create protected endpoints, add `protect: true` to the `meta.openapi` object of each tRPC procedure. You can then authenticate each request with the `createContext` function using the `Authorization` header with the `Bearer` scheme.
@@ -221,6 +201,8 @@ const users: User[] = [
221201

222202
export type Context = { user: User | null };
223203

204+
const t = initTRPC.context<Context>().meta<OpenApiMeta>().create();
205+
224206
export const createContext = async ({ req, res }): Promise<Context> => {
225207
let user: User | null = null;
226208
if (req.headers.authorization) {
@@ -230,16 +212,17 @@ export const createContext = async ({ req, res }): Promise<Context> => {
230212
return { user };
231213
};
232214

233-
export const appRouter = trpc.router<Context, OpenApiMeta>().query('sayHello', {
234-
meta: { openapi: { enabled: true, method: 'GET', path: '/say-hello', protect: true /* 👈 */ } },
235-
input: z.void(), // no input expected
236-
output: z.object({ greeting: z.string() }),
237-
resolve: ({ input, ctx }) => {
238-
if (!ctx.user) {
239-
throw new trpc.TRPCError({ message: 'User not found', code: 'UNAUTHORIZED' });
240-
}
241-
return { greeting: `Hello ${ctx.user.name}!` };
242-
},
215+
export const appRouter = t.router({
216+
sayHello: t.procedure
217+
.meta({ openapi: { method: 'GET', path: '/say-hello', protect: true /* 👈 */ } })
218+
.input(z.void()) // no input expected
219+
.output(z.object({ greeting: z.string() }))
220+
.query(({ input, ctx }) => {
221+
if (!ctx.user) {
222+
throw new trpc.TRPCError({ message: 'User not found', code: 'UNAUTHORIZED' });
223+
}
224+
return { greeting: `Hello ${ctx.user.name}!` };
225+
}),
243226
});
244227
```
245228

@@ -250,7 +233,7 @@ const res = await fetch('http://localhost:3000/say-hello', {
250233
method: 'GET',
251234
headers: { Authorization: 'Bearer usr_123' } /* 👈 */,
252235
});
253-
const body = await res.json(); /* { ok: true, data: { greeting: 'Hello James!' } } */
236+
const body = await res.json(); /* { greeting: 'Hello James!' } */
254237
```
255238

256239
## Examples
@@ -281,14 +264,26 @@ app.listen(3000);
281264
Please see [full example here](examples/with-nextjs).
282265

283266
```typescript
284-
// pages/api/[trpc].ts
267+
// pages/api/[...trpc].ts
285268
import { createOpenApiNextHandler } from 'trpc-openapi';
286269

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

289272
export default createOpenApiNextHandler({ router: appRouter });
290273
```
291274

275+
#### With AWS Lambda
276+
277+
Please see [full example here](examples/with-serverless).
278+
279+
```typescript
280+
import { createOpenApiAwsLambdaHandler } from 'trpc-openapi';
281+
282+
import { appRouter } from './appRouter';
283+
284+
export const openApi = createOpenApiAwsLambdaHandler({ router: appRouter });
285+
```
286+
292287
## Types
293288

294289
#### GenerateOpenApiDocumentOptions
@@ -310,7 +305,7 @@ Please see [full typings here](src/types.ts).
310305

311306
| Property | Type | Description | Required | Default |
312307
| ------------- | ------------------- | ------------------------------------------------------------------------------------------------------------ | -------- | ----------- |
313-
| `enabled` | `boolean` | Exposes this procedure to `trpc-openapi` adapters and on the OpenAPI document. | `true` | `false` |
308+
| `enabled` | `boolean` | Exposes this procedure to `trpc-openapi` adapters and on the OpenAPI document. | `false` | `true` |
314309
| `method` | `HttpMethod` | HTTP method this endpoint is exposed on. Value can be `GET`, `POST`, `PATCH`, `PUT` or `DELETE`. | `true` | `undefined` |
315310
| `path` | `string` | Pathname this endpoint is exposed on. Value must start with `/`, specify path parameters using `{}`. | `true` | `undefined` |
316311
| `protect` | `boolean` | Requires this endpoint to use an `Authorization` header credential with `Bearer` scheme on OpenAPI document. | `false` | `false` |
@@ -329,12 +324,11 @@ Please see [full typings here](src/adapters/node-http/core.ts).
329324
| `createContext` | `Function` | Passes contextual (`ctx`) data to procedure resolvers. | `false` |
330325
| `responseMeta` | `Function` | Returns any modifications to statusCode & headers. | `false` |
331326
| `onError` | `Function` | Called if error occurs inside handler. | `false` |
332-
| `teardown` | `Function` | Called after each request is completed. | `false` |
333327
| `maxBodySize` | `number` | Maximum request body size in bytes (default: 100kb). | `false` |
334328

335329
---
336330

337-
_Are you using tRPC v10? See our [`next`](https://github.com/jlalmes/trpc-openapi/tree/next) branch._
331+
_Still using tRPC v9? See our [`.interop()`](examples/with-interop) example._
338332

339333
## License
340334

examples/with-express/package.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,23 @@
66
"dev": "ts-node-dev --respawn --transpile-only --exit-child ./src/index.ts"
77
},
88
"dependencies": {
9-
"@trpc/server": "^9.27.2",
9+
"@trpc/server": "^10.3.0",
1010
"cors": "^2.8.5",
11-
"express": "^4.18.1",
11+
"express": "^4.18.2",
1212
"jsonwebtoken": "^8.5.1",
13-
"swagger-ui-express": "^4.5.0",
14-
"uuid": "^8.3.2",
15-
"zod": "^3.18.0"
13+
"swagger-ui-express": "^4.6.0",
14+
"uuid": "^9.0.0",
15+
"zod": "^3.19.1"
1616
},
1717
"devDependencies": {
1818
"@types/cors": "^2.8.12",
19-
"@types/express": "^4.17.13",
19+
"@types/express": "^4.17.14",
2020
"@types/jsonwebtoken": "^8.5.9",
21-
"@types/node": "^18.7.13",
21+
"@types/node": "^18.11.9",
2222
"@types/swagger-ui-express": "^4.1.3",
2323
"@types/uuid": "^8.3.4",
2424
"ts-node": "^10.9.1",
2525
"ts-node-dev": "^2.0.0",
26-
"typescript": "^4.7.4"
26+
"typescript": "^4.9.3"
2727
}
2828
}

examples/with-express/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@ app.use('/api', createOpenApiExpressMiddleware({ router: appRouter, createContex
2222
app.use('/', swaggerUi.serve);
2323
app.get('/', swaggerUi.setup(openApiDocument));
2424

25-
app.listen(3001, () => {
26-
console.log('Server started on http://localhost:3001');
25+
app.listen(3000, () => {
26+
console.log('Server started on http://localhost:3000');
2727
});

examples/with-express/src/openapi.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export const openApiDocument = generateOpenApiDocument(appRouter, {
77
title: 'Example CRUD API',
88
description: 'OpenAPI compliant REST API built using tRPC with Express',
99
version: '1.0.0',
10-
baseUrl: 'http://localhost:3001/api',
10+
baseUrl: 'http://localhost:3000/api',
1111
docsUrl: 'https://github.com/jlalmes/trpc-openapi',
1212
tags: ['auth', 'users', 'posts'],
1313
});

0 commit comments

Comments
 (0)