Migration from trpc-to-openapi
Migrate from trpc-to-openapi to trpc-rest.
If you're using trpc-to-openapi, migrating is straightforward. The API is intentionally similar.
1. Swap the package
pnpm remove trpc-to-openapi
pnpm add trpc-rest2. Update imports
// Before
import { createOpenApiHttpHandler } from 'trpc-to-openapi';
import { generateOpenApiDocument } from 'trpc-to-openapi';
// After
import { createOpenApiFetchHandler } from 'trpc-rest';
import { generateOpenApiDocument } from 'trpc-rest';3. Replace the handler
The main difference: createOpenApiFetchHandler takes a Fetch Request and returns a Response. No framework-specific adapters.
// Before (Express adapter)
app.use('/api', createOpenApiHttpHandler({ router: appRouter, createContext }));
// After (any framework — just pass a Request)
const response = await createOpenApiFetchHandler({
router: appRouter,
endpoint: '/api',
req: request,
createContext,
});Next.js App Router
import { createOpenApiFetchHandler } from 'trpc-rest';
const handler = async (req: Request) => {
return createOpenApiFetchHandler({
router: appRouter,
endpoint: '/api',
req,
createContext: async ({ req }) => createContext(req),
});
};
export { handler as GET, handler as POST, handler as PUT, handler as PATCH, handler as DELETE };4. Update the generator
The generator API is the same. errorSchemas and extensions are new features you can optionally adopt.
5. Remove patches
If you had pnpm patches for trpc-to-openapi, remove them:
rm patches/trpc-to-openapi*.patchKey differences
| Behavior | trpc-to-openapi | trpc-rest |
|---|---|---|
| Handler input | Framework-specific | Fetch Request |
| Procedure invocation | Internal calling | createCaller (full middleware) |
| Bodyless POST | 415 error | Works correctly |
| Error response caching | Shared by status code | No caching issue |
| Optional param descriptions | Stripped by unwrapping | Preserved |
| Error format | Nested data.error | Flat { message, code, issues? } |
What's NOT supported
- Express/Fastify adapters — use their Fetch compatibility layer instead
- Content negotiation beyond JSON and form-urlencoded