Type-safe mock servers for ts-rest, oRPC, and tRPC API contracts, powered by MSW and Zod schema generation.
api-mock automatically generates realistic fake responses from your Zod schemas so you can start testing immediately — then override individual endpoints with static values or callback functions when you need fine-grained control.
- Zero boilerplate — pass your contract and get a running mock server.
- Auto-generated responses — every route is pre-populated with random data that satisfies its Zod schema (via
@anatine/zod-mock). - Full type safety — paths, status codes, request bodies, and response bodies are all inferred from the contract; typos are caught at compile time.
- Per-test overrides — override any route with a static response or a callback that receives the request info and a pre-generated default.
- Multi-framework — first-class support for ts-rest, oRPC, and tRPC contracts.
- Nested routers — deeply nested contracts are flattened automatically.
- All HTTP methods — GET, POST, PUT, PATCH, and DELETE (including bodyless responses).
- Escape hatch — access the underlying MSW
serverandhttphelper for custom handlers. - Adapter architecture — the generic MSW + Zod plumbing (
mockHttpApi) is separated from framework adapters, making it straightforward to add support for additional contract libraries.
Install the core package and its peer dependencies:
npm install --save-dev api-mock @anatine/zod-mock @faker-js/faker msw zodThen install the peer dependency for the framework(s) you use:
# For ts-rest
npm install --save-dev @ts-rest/core
# For oRPC
npm install --save-dev @orpc/contract
# For tRPC
npm install --save-dev @trpc/serverNote:
@ts-rest/core,@orpc/contract, and@trpc/serverare all optional peer dependencies. You only need the one(s) matching the adapters you use.
You can import everything from the main entry point, or use the dedicated sub-path exports to only pull in the adapter you need:
// Everything (barrel export)
import { mockTsRest, mockOrpc, mockTrpc } from 'api-mock';
// Per-adapter (tree-shakeable, avoids loading unused adapters)
import { mockTsRest } from 'api-mock/ts-rest';
import { mockOrpc } from 'api-mock/orpc';
import { mockTrpc } from 'api-mock/trpc';
// Generic HTTP layer (for building custom adapters)
import { createMockServer, registerOverride } from 'api-mock/http';import { initContract } from '@ts-rest/core';
import { mockTsRest } from 'api-mock';
import { z } from 'zod';
const c = initContract();
const contract = c.router({
getUsers: {
method: 'GET',
path: '/users',
responses: {
200: z.array(z.object({ id: z.string(), name: z.string() })),
},
},
createUser: {
method: 'POST',
path: '/users',
body: z.object({ name: z.string() }),
responses: {
201: z.object({ id: z.string(), name: z.string() }),
},
},
});
// Create the mock server — all routes are immediately available
// with randomly generated responses.
const server = mockTsRest('http://localhost:3000', contract);
// Override specific endpoints as needed:
server.get('/users', 200, [
{ id: '1', name: 'Alice' },
{ id: '2', name: 'Bob' },
]);
// Use in your tests, then clean up:
server.close();import { oc } from '@orpc/contract';
import { mockOrpc } from 'api-mock';
import { z } from 'zod';
const contract = {
getUsers: oc
.route({ method: 'GET', path: '/users' })
.output(z.array(z.object({ id: z.string(), name: z.string() }))),
createUser: oc
.route({ method: 'POST', path: '/users', successStatus: 201 })
.input(z.object({ name: z.string() }))
.output(z.object({ id: z.string(), name: z.string() })),
};
// Create the mock server — all procedures are immediately available
// with randomly generated responses.
const server = mockOrpc('http://localhost:3000', contract);
// Override by procedure key (fully type-safe):
server.mock('getUsers', [
{ id: '1', name: 'Alice' },
{ id: '2', name: 'Bob' },
]);
// Use in your tests, then clean up:
server.close();import { initTRPC } from '@trpc/server';
import { mockTrpc } from 'api-mock';
import { z } from 'zod';
const t = initTRPC.create();
const appRouter = t.router({
getUser: t.procedure
.input(z.object({ id: z.string() }))
.output(z.object({ id: z.string(), name: z.string() }))
.query(({ input }) => ({ id: input.id, name: 'Alice' })),
createUser: t.procedure
.input(z.object({ name: z.string() }))
.output(z.object({ id: z.string(), name: z.string() }))
.mutation(({ input }) => ({ id: '1', name: input.name })),
});
// Create the mock server — procedures with .output() Zod schemas
// get auto-generated responses. Include any tRPC path prefix in the host.
const server = mockTrpc('http://localhost:3000/trpc', appRouter);
// Override by procedure key (fully type-safe):
server.mock('getUser', { id: '1', name: 'Alice' });
// Use in your tests, then clean up:
server.close();The simplest override provides a literal response value:
server.get('/users', 200, [{ id: '1', name: 'Alice' }]);
server.post('/users', 201, { id: '2', name: 'Bob' });
server.put('/users/:id', 200, { id: '2', name: 'Bob Updated' });
server.patch('/users/:id', 200, { id: '2', name: 'Bob Patched' });For dynamic responses, pass a callback. It receives the MSW request info
(with a typed json() method) and a pre-generated default response:
server.post('/users', 201, async ({ request }, defaultResponse) => {
const body = await request.json();
return {
...defaultResponse, // includes randomly generated fields
name: body.name, // override specific fields
};
});You can simulate error responses, including status codes not defined in your contract (e.g. 500 from a load balancer):
server.get('/users', 404, { error: 'Not found' });
server.get('/users', 500, { message: 'Internal server error' });ts-rest's c.noBody() is fully supported:
server.delete('/users/:id', 204, undefined);Override a procedure by its key path in the contract:
server.mock('getUsers', [{ id: '1', name: 'Alice' }]);
server.mock('nested.getUser', { id: '1', name: 'Alice' });For dynamic responses, pass a callback with access to the request and a pre-generated default:
server.mock('createUser', async ({ request }, defaultResponse) => {
const body = await request.json();
return {
...defaultResponse,
name: body.name,
};
});Procedures without an output schema respond with an empty body:
server.mock('deleteUser', undefined);Procedures that don't specify .route({ path }) get a path derived from
their key in the contract (e.g. planet.find → /planet/find) with the
default method POST, matching oRPC's built-in RPC routing behaviour.
Override a procedure by its key path in the router:
server.mock('getUser', { id: '1', name: 'Alice' });
server.mock('post.byId', { id: '1', title: 'Hello', body: 'World' });For dynamic responses, pass a callback with access to the request and a pre-generated default:
server.mock('createUser', async ({ request }, defaultResponse) => {
const body = await request.json();
return {
...defaultResponse,
name: body.name,
};
});tRPC procedures that define .output(zodSchema) get auto-generated mock
responses, just like ts-rest and oRPC. Procedures that rely on TypeScript
inference from the handler's return type (no .output() call) respond with
an empty body by default — override them with mock():
// Procedure defined with .output() → auto-generated response ✓
// Procedure without .output() → empty body, override with:
server.mock('health', { status: 'ok' });tRPC queries are intercepted as GET requests and mutations as POST,
matching tRPC's HTTP transport. Procedure paths use dot notation for nested
routers (e.g. post.create → ${host}/post.create).
Both adapters expose the underlying MSW primitives for routes outside your contract:
server.use(
server.msw.http.get('http://localhost:3000/health', () => {
return new Response('ok');
}),
);Request bodies for mutation routes (POST, PUT, PATCH) are automatically
validated against the contract's Zod schema (ts-rest and oRPC). A ZodError
is thrown if the request body doesn't match, which helps catch test setup
mistakes. tRPC procedures skip body validation since tRPC queries use query
params and mutations use a wrapped body format.
Creates and starts a mock server for ts-rest contracts.
| Parameter | Type | Description |
|---|---|---|
host |
string |
Base URL to intercept (e.g. 'http://localhost:3000'). |
...contracts |
AppRouter[] |
One or more ts-rest router contracts. |
Returns an object with:
| Property / Method | Description |
|---|---|
get(path, status, resolver, options?) |
Override a GET route. |
post(path, status, resolver, options?) |
Override a POST route. |
put(path, status, resolver, options?) |
Override a PUT route. |
patch(path, status, resolver, options?) |
Override a PATCH route. |
delete(path, status, resolver, options?) |
Override a DELETE route. |
use(...handlers) |
Register additional MSW request handlers. |
close() |
Stop the mock server and clean up. |
msw.server |
The underlying MSW SetupServerApi instance. |
msw.http |
The MSW http namespace for building custom handlers. |
Creates and starts a mock server for oRPC contracts.
| Parameter | Type | Description |
|---|---|---|
host |
string |
Base URL to intercept (e.g. 'http://localhost:3000'). |
...contracts |
OrpcRouterLike[] |
One or more oRPC contract routers. |
Returns an object with:
| Property / Method | Description |
|---|---|
mock(key, resolver, options?) |
Override a procedure by its dotted key path. |
use(...handlers) |
Register additional MSW request handlers. |
close() |
Stop the mock server and clean up. |
msw.server |
The underlying MSW SetupServerApi instance. |
msw.http |
The MSW http namespace for building custom handlers. |
Creates and starts a mock server for tRPC routers.
| Parameter | Type | Description |
|---|---|---|
host |
string |
Base URL to intercept, including any tRPC path prefix (e.g. 'http://localhost:3000/trpc'). |
...routers |
TrpcRouterLike[] |
One or more tRPC routers created with t.router(). |
Returns an object with:
| Property / Method | Description |
|---|---|
mock(key, resolver, options?) |
Override a procedure by its dotted key path. Queries → GET, mutations → POST. |
use(...handlers) |
Register additional MSW request handlers. |
close() |
Stop the mock server and clean up. |
msw.server |
The underlying MSW SetupServerApi instance. |
msw.http |
The MSW http namespace for building custom handlers. |
Note: Procedures with
.output(zodSchema)get auto-generated defaults. Procedures without.output()respond with an empty body until overridden viamock().
Each override helper accepts a resolver which is either:
- A static value matching the response schema for the given endpoint, or
- A callback
(info, defaultResponse) => responsewhere:infois the MSW resolver info with a typedrequest.json().defaultResponseis a randomly generated value that satisfies the schema.- The return value can be a
Promise.
The codebase is split into a generic layer and framework-specific adapters:
| Module | Responsibility |
|---|---|
mockHttpApi.ts |
Framework-agnostic MSW + Zod plumbing: server lifecycle, default handler creation, override registration, mock data generation, and shared types/errors. |
mockTsRest.ts |
ts-rest adapter: contract-walking, type generics (GetPath, FilterRoute, GetResponseBody, etc.), and the mockTsRest() entry point. Uses HTTP method + path for overrides. |
mockOrpc.ts |
oRPC adapter: contract-walking via the ~orpc property, type generics (ProcedureKeys, InferOrpcOutput, etc.), and the mockOrpc() entry point. Uses dotted procedure keys for overrides. |
mockTrpc.ts |
tRPC adapter: router-record walking via _def.record, type generics (TrpcProcedureKeys, InferTrpcOutput, etc.), and the mockTrpc() entry point. Uses dotted procedure keys for overrides. Queries → GET, mutations → POST. |
To add support for a new contract library, create a new adapter that imports
from mockHttpApi and adds framework-specific type inference and route
extraction. The generic layer handles all MSW interaction.
| Export | Purpose |
|---|---|
createMockServer(handlers) |
Starts an MSW server with default handlers; returns { msw, use, close }. |
createDefaultHandler(host, method, path, status, schema) |
Creates a single default MSW handler for a route. null schema = no body. |
registerOverride(server, host, fn, path, status, schema, resolver, validator, opts) |
Registers an override handler — the runtime core of every HTTP-method helper. |
mockSchema(schema) |
Generates random data conforming to a Zod schema. |
Method, MutationMethod, GetResolver |
Shared types reused by every adapter. |