Skip to content

Mock handlers

@codewithagents/openapi-msw reads your OpenAPI 3.x spec and writes a single handlers.ts file of Mock Service Worker v2 HTTP handlers, one per operation, each returning a Faker-generated body for the operation’s 2xx JSON response. Point it at the same spec your client and server use, drop the handlers into setupWorker (browser) or setupServer (Node and tests), and you have realistic, type-shaped mock responses without hand-writing a single fixture. Output is deterministic: a faker.seed(...) call is written at the top of the file, so the same spec and seed always produce identical values.

Running the generator produces one file in your output directory:

FileContents
handlers.tsOne http.<method>(path, resolver) handler per operation, wrapped in export const handlers = []

Key guarantees:

  • One handler per operation. Every get, post, put, patch, and delete operation in the spec becomes an http.<method> handler. Operations missing responses are skipped.
  • Faker-shaped bodies. Each handler returns a body built from the operation’s 2xx JSON schema, with values generated by Faker according to each field’s type and format (email, uuid, date-time, uri, enums, and so on).
  • Deterministic by default. A faker.seed(<seed>) call (default 42) is emitted at the top of the file, so re-running with the same spec and seed produces identical mock values. Change the seed and re-generate to get a different fixed set.
  • MSW v2 path syntax. OpenAPI {id} path parameters are rewritten to MSW’s colon form :id. Every parameter in a path is converted, so /orgs/{orgId}/repos/{repoId} becomes /orgs/:orgId/repos/:repoId.
  • Prettier-clean output. The generated handlers.ts passes prettier --check with your project’s own Prettier config.
  • OpenAPI 3.1.x is the primary target (including 3.1.1). OpenAPI 3.0.x is supported in practice: 8 of the 13 showcase specs are 3.0.x and all compile under tsc --strict. Supports $ref, allOf, anyOf, oneOf, and enums, with depth and circular-reference guards.
Terminal window
npm i -D @codewithagents/openapi-msw

The generated handlers.ts imports http/HttpResponse from msw and faker from @faker-js/faker. Both are peer dependencies, so install them as regular dependencies in the consumer project:

Terminal window
npm i msw @faker-js/faker

1. Create openapi-msw.config.json in your project root:

{
"input_openapi": "./spec/api.json",
"output": "./src/mocks"
}

2. Run the generator:

Terminal window
npx openapi-msw

Or add a script to package.json:

{
"scripts": {
"generate:mocks": "openapi-msw"
}
}

This writes ./src/mocks/handlers.ts (the output directory is created recursively if missing).

3. Wire the handlers into MSW:

src/mocks/browser.ts
import { setupWorker } from 'msw/browser'
import { handlers } from './handlers'
export const worker = setupWorker(...handlers)

The config file must be valid JSON and must have a .json extension. By default the generator looks for openapi-msw.config.json in the current working directory. Use --config <path> to point at a different file.

Terminal window
npx openapi-msw --config ./config/openapi-msw.config.json
FieldTypeRequiredDefaultDescription
input_openapistringYesn/aPath to your OpenAPI spec (JSON or YAML)
outputstringYesn/aDirectory to write handlers.ts
seednumberNo42Faker seed written into the generated file. Integer >= 0
max_array_itemsnumberNo3Number of items generated for array schemas. Integer >= 1
depth_capnumberNo30Maximum schema-resolution depth before emitting null. Integer >= 1

Only input_openapi and output are required. The three optional keys are validated only when present, and their defaults are applied at generation time if omitted.

Full config example:

{
"input_openapi": "./spec/api.json",
"output": "./src/mocks",
"seed": 42,
"max_array_items": 3,
"depth_cap": 30
}

To generate handlers for more than one spec from a single config, use a top-level projects array. Each entry takes its own input_openapi, output, and optional keys:

{
"projects": [
{ "input_openapi": "./spec/orders.json", "output": "./src/mocks/orders" },
{ "input_openapi": "./spec/billing.json", "output": "./src/mocks/billing", "seed": 7 }
]
}

The CLI takes no positional arguments, only flags:

FlagDescription
--config <path>Path to the config file. Defaults to openapi-msw.config.json in the cwd
--help, -hPrint usage and exit
--version, -vPrint the installed version and exit
Terminal window
# Use openapi-msw.config.json in the current directory
npx openapi-msw
# Point at a config elsewhere
npx openapi-msw --config ./config/openapi-msw.config.json

Given a task API with GET /api/v1/tasks (returns a paged list) and DELETE /api/v1/tasks/{id} (returns 204), the generator writes:

// This file is auto-generated by @codewithagents/openapi-msw, do not edit
import { http, HttpResponse } from 'msw'
import { faker } from '@faker-js/faker'
faker.seed(42)
export const handlers = [
http.get('/api/v1/tasks', () =>
HttpResponse.json({
items: Array.from({ length: 3 }, () => ({
id: faker.string.uuid(),
title: faker.lorem.word(),
status: faker.helpers.arrayElement(['pending', 'in_progress', 'done']),
priority: faker.number.int({ min: 1, max: 1000 }),
assigneeEmail: faker.internet.email(),
createdAt: faker.date.recent().toISOString(),
})),
total: faker.number.int({ min: 1, max: 1000 }),
})
),
http.delete('/api/v1/tasks/:id', () => HttpResponse.json(null, { status: 204 })),
]

The {id} path parameter is rewritten to MSW’s :id, and each field maps to a Faker call chosen from its schema type and format.

SchemaGenerated value
string, format emailfaker.internet.email()
string, format uuidfaker.string.uuid()
string, format date-timefaker.date.recent().toISOString()
string, format datefaker.date.recent().toISOString().slice(0, 10)
string, format uri / urlfaker.internet.url()
string (other)faker.lorem.word()
integer, int32, int64faker.number.int({ min: 1, max: 1000 })
number (other)faker.number.float({ min: 0, max: 1000, fractionDigits: 2 })
booleanfaker.datatype.boolean()
enumfaker.helpers.arrayElement([...literals])
arrayArray.from({ length: maxArrayItems }, () => item)
null / unresolvablenull

The package also exposes a programmatic API for use inside your own scripts:

import { generateHandlers } from '@codewithagents/openapi-msw'
const { filename, content } = generateHandlers(spec, {
seed: 42,
maxArrayItems: 3,
depthCap: 30,
})
// filename === 'handlers.ts'

generate, loadConfig, and loadConfigs are also exported for driving the full config-to-file flow, along with the MswConfig, HandlerGenOptions, and GeneratedFile types.

These rules are not obvious from the config alone, so they are worth keeping in mind:

  • 2xx selection order. The generator prefers 200, then 201, then the first 2xx code with an application/json body. A 200 emits HttpResponse.json(body) with no status argument; any other 2xx code (for example 201) emits HttpResponse.json(body, { status: N }).
  • Empty and non-JSON responses. A 2xx response with no application/json schema emits HttpResponse.json(null, { status: N }) (status from the first 2xx code, defaulting to 204). If that response instead declares a non-JSON content type (text/plain, octet-stream, image, and so on) it emits new HttpResponse(null, { status: N }), so it never falsely claims application/json. Only application/json responses get a Faker body.
  • allOf is merged; anyOf/oneOf pick the first. Properties of all allOf members are merged into one object and their required arrays concatenated. For anyOf and oneOf the generator picks the first concrete (non-$ref) member; if all members are refs it resolves the first one.
  • depth_cap bounds recursion. The depth counter increments only on $ref resolution and on allOf/anyOf/oneOf resolution. Array nesting and plain property access do not increment it. When depth exceeds the cap, the generator emits null /* depth cap reached */.
  • Circular refs terminate cleanly. A $ref back to an already-visited schema name emits null /* circular ref: depth cap reached */, distinct from the depth-cap comment, so self-referential schemas do not loop forever.
  • Type unions use the first element. OpenAPI 3.1 lets type be an array; the generator uses only the first element. Schemas with no type fall back to object generation when properties is present, otherwise emit null.
  • operationId is not required. A handler is driven by its path and method, so operations without an operationId still generate.

Types and fetch client

openapi-zod-ts generates the typed models.ts and fetch client your app calls. openapi-msw mocks those same calls in the browser and in tests.

Learn more

React Query hooks

Add @codewithagents/openapi-react-query to generate typed useQuery and useMutation hooks, then mock their requests with the generated handlers during development and tests.

Learn more

Server interface

Add @codewithagents/openapi-server to implement the same spec on the server with a typed service interface and an optional fastify, hono, or express router.

Learn more