trpc-to-openapi

repository·master·Indexed 43 days ago

https://github.com/mcampa/trpc-to-openapi

Provides OpenAPI (Swagger) support for tRPC v11, allowing developers to incrementally adopt REST endpoints for existing tRPC procedures. Includes adapters for Express, Fastify, Next.js (App and Pages Router), and standard Node.js HTTP servers. Version 3.3.0.

Tokens
6.4K
Snippets
26
Records
34
Agent score
54%

What's inside trpc-to-openapi

  1. How request bodies and query parameters are handled

    master

    The library determines where to look for input based on the HTTP method:

    • GET / DELETE: Inputs are accepted via URL query parameters.
    • POST / PATCH / PUT: Inputs are accepted via the request body with an application/json content type.

    Coercion: Query and path parameters are received as strings. The library automatically attempts to coerce them into number, boolean, bigint, and date. For other types like object or array, use z.preprocess() in your Zod schema.

  2. How path parameters work in OpenAPI procedures

    master

    To define path parameters, wrap the input name in curly brackets {} within the meta.openapi.path string. The corresponding input must be defined in the tRPC procedure's .input() using Zod.

    // 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',
    });
  3. Run the with-express example project

    master

    To run the with-express example, you must first install the dependencies and build the workspace. Use pnpm to manage the monorepo commands. After building, you can start the development server specifically for the with-express package using the -w flag.

    pnpm install
    pnpm run build
    pnpm run dev -w with-express
  4. Run the Next.js App Router example

    master

    To run the demonstration project using Next.js App Router, ensure you are in the /trpc-to-openapi root directory of the monorepo. You must install dependencies, build the workspace, and then run the development command for the specific with-nextjs workspace.

    Note: This example is part of a monorepo and requires pnpm.

    pnpm install
    pnpm run build
    pnpm run dev -w with-nextjs
  5. Configure Authorization for protected endpoints

    master

    To protect an endpoint, add protect: true to the meta.openapi object. By default, the library expects authentication via the Authorization header using the Bearer scheme. You handle the actual authentication logic within your tRPC createContext function.

    If you need custom authentication methods (e.g., cookies or custom headers), you can specify securitySchemes in the OpenAPI metadata.

    // Server
    export const appRouter = t.router({
      sayHello: t.procedure
        .meta({ openapi: { method: 'GET', path: '/say-hello', protect: true } })
        .input(z.void())
        .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' }
    });
  6. Filter procedures in OpenAPI output

    master

    You can selectively include or exclude procedures from your generated OpenAPI document using the filter option in generateOpenApiDocument. The filter function receives a context object containing the procedure's metadata. Return true to include the procedure, or false to exclude it.

    To get proper types for your custom metadata within the filter function, pass a metadata generic to generateOpenApiDocument.

    // Define your custom metadata type
    interface MyMeta {
      isPublic: boolean;
    }
    
    const appRouter = t.router({
      publicProc: t.procedure
        .meta({ openapi: { method: 'GET', path: '/public' }, isPublic: true })
        .query(() => ({ result: 'public' })),
      privateProc: t.procedure
        .meta({ openapi: { method: 'GET', path: '/private' }, isPublic: false })
        .query(() => ({ result: 'private' })),
    });
    
    // Only include procedures where isPublic is true
    const openApiDocument = generateOpenApiDocument<MyMeta>(appRouter, {
      title: 'My API',
      version: '1.0.0',
      baseUrl: 'http://localhost:3000',
      filter: ({ metadata }) => metadata.isPublic === true,
    });
    
    // openApiDocument.paths will only include '/public'
  7. Configure OpenApiMeta in your tRPC instance

    master

    To enable OpenAPI support, you must pass OpenApiMeta to the meta generic when initializing your tRPC instance using initTRPC.

    import { initTRPC } from '@trpc/server';
    import { OpenApiMeta } from 'trpc-to-openapi';
    
    const t = initTRPC.meta<OpenApiMeta>().create();
  8. Understand the OpenAPI response format

    master

    When consuming the generated OpenAPI endpoints, responses follow a union type of either a successful data payload or a structured error response.

    Success Response: Returns the data type D defined by the procedure's output.

    Error Response (OpenApiErrorResponse): If the request fails, the response includes:

    • message: A string describing the error.
    • code: The tRPC error code (from @trpc/server/rpc).
    • issues: (Optional) An array of Zod issues if the error was caused by validation failure.