OpenAPI Generator
Generate OpenAPI from your oRPC router
Installation
npm i @orpc/openapi
Define the Route
import { os, ORPCError } from '@orpc/server'
import { oz } from '@orpc/zod'
import { z } from 'zod'
export type Context = { user?: { id: string } }
// global pub, authed completely optional
export const pub = os.context<Context>()
export const authed = pub.use(({ context, path, next }, input) => {
/** put auth logic here */
return next({})
})
export const router = pub.router({
getUser: os
.input(
z.object({
id: z.string(),
}),
)
.handler(async ({ input, context }) => {
return {
id: input.id,
name: 'example',
}
}),
post: pub.prefix('/posts').router({
getPost: pub
.route({
path: '/{id}', // custom your OpenAPI
method: 'GET',
})
.input(
z.object({
id: z.string(),
}),
)
.output(
z.object({
id: z.string(),
title: z.string(),
description: z.string(),
}),
)
.handler(({ input, context }) => {
return {
id: 'example',
title: 'example',
description: 'example',
}
}),
createPost: authed
.input(
z.object({
title: z.string(),
description: z.string(),
thumb: oz.file().type('image/*'),
}),
)
.handler(async ({ input, context }) => {
input.thumb // file upload out of the box
return {
id: 'example',
title: input.title,
description: input.description,
}
}),
}),
})
Generate the OpenAPI Specification
To generate an OpenAPI specification, you need either the type of the router you intend to use or the contract.
import { OpenAPIGenerator } from '@orpc/openapi'
import { ZodToJsonSchemaConverter } from '@orpc/zod'
import { router } from 'examples/server'
import { contract } from 'examples/contract'
const openAPIGenerator = new OpenAPIGenerator({
schemaConverters: [
new ZodToJsonSchemaConverter(),
],
})
const spec = await openAPIGenerator.generate(contract /* or router */, {
info: {
title: 'My App',
version: '0.0.0',
},
})
console.log(JSON.stringify(spec, null, 2))
Here is the result:
{
info: {
title: 'My App',
version: '0.0.0',
},
openapi: '3.1.0',
paths: {
'/getting': {
post: {
operationId: 'getting',
requestBody: {
required: false,
content: {
'application/json': {
schema: {
type: 'object',
properties: {
name: {
type: 'string',
},
},
required: ['name'],
},
},
},
},
responses: {
'200': {
description: 'OK',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
message: {
type: 'string',
},
},
required: ['message'],
},
},
},
},
},
},
},
'/posts/{id}': {
get: {
operationId: 'post.find',
parameters: [
{
name: 'id',
in: 'path',
required: true,
schema: {
type: 'string',
},
},
],
responses: {
'200': {
description: 'OK',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
id: {
type: 'string',
},
title: {
type: 'string',
},
description: {
type: 'string',
},
},
required: ['id', 'title', 'description'],
},
},
},
},
},
},
},
'/post/create': {
post: {
operationId: 'post.create',
requestBody: {
required: false,
content: {
'multipart/form-data': {
schema: {
type: 'object',
properties: {
title: {
type: 'string',
},
description: {
type: 'string',
},
thumb: {
type: 'string',
contentMediaType: 'image/*',
},
},
required: ['title', 'description', 'thumb'],
},
},
},
},
responses: {
'200': {
description: 'OK',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
id: {
type: 'string',
},
title: {
type: 'string',
},
description: {
type: 'string',
},
},
required: ['id', 'title', 'description'],
},
},
},
},
},
},
},
},
}