MSW Integration
Mock oRPC procedures at the network level with typed MSW request handlers, reusing the real RPC or OpenAPI runtime for serialization and validation.
Installation
npm install @orpc/experimental-msw@betapnpm add @orpc/experimental-msw@betayarn add @orpc/experimental-msw@betabun add @orpc/experimental-msw@betaSetup
Create MSW utils from a router contract or an implemented router (convert lazy routers with unlazyRouter first). The handler option creates the fetch handler that serves each mock. Configure it like your production handler, so serialization, validation, and error envelopes behave exactly like your real server.
import { createHTTPUtils } from '@orpc/experimental-msw'
import { RPCHandler } from '@orpc/server/fetch'
export const mock = createHTTPUtils(contract, {
prefix: '/rpc',
handler: router => new RPCHandler(router),
})
Set prefix to the prefix your link sends requests to. Any origin matches by default; narrow it with the origin option, which supports MSW wildcards.
Mocking Procedures
The .handler method creates an MSW request handler that resolves a procedure. The input your mock receives and the output it returns are validated and serialized by the created fetch handler, exactly like on a real server.
import { setupServer } from 'msw/node'
const server = setupServer(
mock.planet.list.handler(({ input }) => [
{ id: 1, name: 'Earth' },
]),
)
server.listen()
To access request details, such as the raw request, expose them through the context option.
Mocking Errors
The .error method creates an MSW request handler that rejects a procedure with one of its defined errors, serialized exactly like a server-thrown error. For dynamic or arbitrary errors, use .handler and throw the errors constructors or any ORPCError:
import { ORPCError } from '@orpc/client'
const handlers = [
mock.planet.find.error('NOT_FOUND', { data: { id: 123 } }),
mock.planet.update.handler(({ input, errors }) => {
throw errors.CONFLICT({ data: { id: input.id } })
}),
mock.planet.delete.handler(() => {
throw new ORPCError('SERVICE_UNAVAILABLE')
}),
]
Mocking Loading States
The .loading method creates an MSW request handler that never resolves, useful for testing loading states, for example in Storybook stories:
export const Loading: Story = {
parameters: {
msw: {
handlers: [mock.planet.list.loading()],
},
},
}
Passthrough
The .passthrough method creates an MSW request handler that performs matching requests against the real server as-is, useful to exempt specific procedures from mocking, for example while onUnhandledRequest treats everything else as an error:
const handlers = [
mock.planet.list.handler(() => []),
mock.planet.find.passthrough(), // hits the real server
]
Advanced Configuration
All handler behavior is configured through the handler option, so mocks can mirror your production setup exactly, such as plugins, a custom serializer, or allowMethods if your client sends GET requests over the RPC protocol:
import { RPCHandler } from '@orpc/server/fetch'
import { ResponseHeadersHandlerPlugin } from '@orpc/server/plugins'
const mock = createHTTPUtils(contract, {
prefix: '/rpc',
handler: router => new RPCHandler(router, {
plugins: [new ResponseHeadersHandlerPlugin()],
}),
})
The context option controls the context passed to the created handler on each request, and mock handlers receive it as context, enabling context-driven behaviors such as the Response Headers Plugin.
import { ResponseHeadersHandlerPlugin, type ResponseHeadersHandlerPluginContext } from '@orpc/server/plugins'
interface MockServerContext extends ResponseHeadersHandlerPluginContext {
reqHeaders: Headers
}
const mock = createHTTPUtils(contract, {
context: (info): MockServerContext => ({ reqHeaders: info.request.headers }),
handler: router => new RPCHandler(router, {
plugins: [new ResponseHeadersHandlerPlugin()],
}),
})
const handlers = [
mock.planet.list.handler(({ context }) => {
const locale = context.reqHeaders.get('accept-language') ?? 'en'
context.resHeaders?.set('content-language', locale)
return []
}),
]
You can also disable input or output validation of the mocked data:
const mock = createHTTPUtils(contract, {
handler: router => new RPCHandler(router),
disableInputValidation: true,
disableOutputValidation: true,
})
Each mock serves a router containing only the procedure being mocked. Requests the created handler does not match simply fall through to other MSW handlers.
Limitations
Requests sent through the Batch Requests Plugin cannot be mocked. Each mock serves a router containing only its own procedure, so even a handler configured with the batch plugin cannot resolve the other procedures bundled into the same HTTP request. Disable batching when mocking with MSW.