@codewithagents/openapi-msw

中文文档
0.3.0发布于 2026/11/03MITTypeScriptES Module构建来源已验证近 30 天下载 3226

Generate MSW v2 handlers with seeded Faker mock data from OpenAPI 3.1 specs

原始使用文档
文档目录10 节
  1. Install
  2. Quick start
  3. Generated output
  4. Config reference
  5. Fixture builders ( fixtures: true )
  6. How bodies are generated
  7. Non-obvious behavior
  8. Programmatic API
  9. Part of the openapi-zod-ts suite
  10. License

@codewithagents/openapi-msw

npm CI codecov CodeQL

📖 Full documentation

@codewithagents/openapi-msw generates MSW v2 mock handlers with seeded Faker data from your OpenAPI 3.1 spec: point it at a spec, get a type-safe handlers.ts you can drop into a browser worker or a Node mock server instantly.

  • One handler per operation: every get, post, put, patch, and delete operation becomes an http.<method>(path, resolver) entry in an exported handlers array.
  • Seeded, deterministic mocks: faker.seed(<seed>) (default 42) is written into the generated file, so every run with the same spec and seed produces identical mock values. Change the seed, re-generate, get a new but still stable dataset.
  • MSW v2 path syntax: OpenAPI {id} path params are rewritten to MSW colon style :id. Every param in a path is converted (/orgs/{orgId}/repos/{repoId} becomes /orgs/:orgId/repos/:repoId).
  • Works in the browser and in Node: handler paths are anchored to an origin (the * wildcard by default, or your base_url), so they match the absolute URLs Node's fetch sends under setupServer as well as browser requests under setupWorker.
  • Schema-aware bodies: each handler returns a Faker-generated body shaped from the operation's 2xx JSON response schema, honouring string formats, enums, arrays, and nested objects.
  • Opt-in fixture builders: fixtures: true also writes fixtures.ts, one typed a<Name>(overrides?) builder per component schema for inline, per-test handlers. See Fixture builders.
  • Prettier-clean output: the generated handlers.ts passes prettier --check out of the box.
  • OpenAPI 3.x: 3.1.x is the primary target; 3.0.x is supported in practice (8 of the 13 showcase specs are 3.0.x and all compile under tsc --strict).

Install

pnpm add -D @codewithagents/openapi-msw
# or
npm install -D @codewithagents/openapi-msw

The generated handlers.ts imports from msw and @faker-js/faker directly, so both are peer dependencies you install yourself:

pnpm add msw @faker-js/faker
Peer dependencyRange
msw^2.0.0 || ^3.0.0
@faker-js/faker^10.5.0

This package depends on openapi-zod-ts for spec parsing and shared config plumbing, but it does not require you to generate models or a client. Run it standalone or alongside the rest of the suite.


Quick start

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

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

Only input_openapi and output are required. The three numeric fields are optional and default to the values shown above.

2. Run the generator:

npx openapi-msw
# or point at a config elsewhere:
npx openapi-msw --config ./config/openapi-msw.config.json

The CLI takes no positional arguments. Available flags are --config <path>, --help / -h, and --version / -v. When --config is given, relative paths in the config resolve from the config file's directory.

3. handlers.ts appears in your output directory:

The output directory is created recursively if it does not exist. Wire the handlers into MSW as usual:

// browser worker
import { setupWorker } from 'msw/browser'
import { handlers } from './src/mocks/handlers'

export const worker = setupWorker(...handlers)
// Node / test server
import { setupServer } from 'msw/node'
import { handlers } from './src/mocks/handlers'

export const server = setupServer(...handlers)

Generated output

Given a task API with GET /api/v1/tasks (returns a paged list) and DELETE /api/v1/tasks/{id} (204 No Content), the generated handlers.ts looks like this:

// 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 })),
]

A committed, drift-checked sample lives at packages/integration/generated/handlers.ts, generated from packages/integration/spec/api.json.


Config reference

openapi-msw.config.json:

FieldRequiredDefaultDescription
input_openapiYesn/aPath to the OpenAPI 3.x spec (JSON or YAML)
outputYesn/aDirectory to write handlers.ts (created recursively)
seedNo42Passed to faker.seed() in the generated file. Must be an integer >= 0
max_array_itemsNo3Length of arrays in generated mocks. Must be an integer >= 1
depth_capNo30Schema recursion depth before bailing out to null. Must be an integer >= 1
base_urlNo* wildcard originOrigin (optionally with a base path) prefixed to every handler path, e.g. "https://api.test" gives https://api.test/api/v1/tasks. Trailing slashes are dropped. Must be a non-empty string

| fixtures | No | false | When true, also write fixtures.ts (per-schema fixture builders). Must be a boolean | | models_import | No | "./models.js" | Module specifier fixtures.ts imports the model types from (the openapi-zod-ts models.ts). Must be a non-empty string |

The optional keys are validated only when present. Every key except projects can also be set per entry inside a projects array.

Multi-spec generation. Replace the top-level fields with a projects array to generate one handlers.ts per spec. Mixing a projects array with top-level input_openapi / output throws.

{
  "projects": [
    { "input_openapi": "./specs/public.json", "output": "./src/mocks/public" },
    { "input_openapi": "./specs/admin.json", "output": "./src/mocks/admin" }
  ]
}

Fixture builders (fixtures: true)

handlers.ts and fixtures.ts solve two different problems:

FileRoleUse it for
handlers.tsMock layer. A full handler set, one per operation, with one seeded datasetA dev worker (setupWorker), a demo backend, or the baseline setupServer(...handlers) that every test starts from
fixtures.tsPer-test data. One builder per component schema that returns a complete, valid valueInline handlers inside a single test (server.use(...)) where the test controls the exact response

Component tests that declare inline handlers need complete objects of the schema types. Hand-written fixtures drift from the spec; generated builders do not. Turn them on with "fixtures": true, and fixtures.ts is written next to handlers.ts:

import { http, HttpResponse } from 'msw'
import { server } from './mocks/server'
import { aTask } from './mocks/fixtures.js'

it('shows the task title', async () => {
  const TASK = aTask({ title: 'Write docs', status: 'done' })
  server.use(http.get('*/api/v1/tasks/:id', () => HttpResponse.json(TASK)))
  // render, then assert on TASK.title ...
})

For every schema in components.schemas the generator emits:

export function aTask(overrides?: Partial<Task>, variant = 0): Task
aTask.full = (overrides?: Partial<Task>, variant = 0): Task => ...
  • Naming. a<Name>, or an<Name> when the type name starts with a vowel (anOwner, anUpdateTaskRequest). <Name> is the exact type name openapi-zod-ts emits in models.ts.
  • Typed against your models. fixtures.ts does import type { ... } from './models.js' (the openapi-zod-ts output). overrides is Partial<Name>, so a wrong value or an unknown key is a compile error. Point models_import elsewhere when the models do not live next to the mocks, e.g. "models_import": "../api/models.js".
  • Shallow merge. aTask({ owner }) replaces owner wholesale; it does not merge into a generated owner.
  • Required-only by default. aTask() fills only the properties listed in required (recursively, in nested objects too). aTask.full() fills optional properties too, on the object itself and on objects built by one nested builder level (in aPet.full() a nested owner is full as well; anything below it is required-only), so .full() stays bounded on large, deeply connected specs. writeOnly properties are always left out, matching the read-shape model type.
  • Deterministic per call. Each call reseeds a private Faker instance (separate from the global faker that handlers.ts uses) from (seed, builder, variant). The same call always returns the same value, regardless of which builders ran before it or in which order tests run, so snapshots and assertions stay stable. Relative dates (date-time / date fields) are computed against a fixed reference date (2025-01-01T00:00:00.000Z) rather than "now", so they are stable across runs and days too. Pass a different variant to get a different, still deterministic value, for example two distinct items in one list: [aTask({}, 0), aTask({}, 1)].
  • Nested schemas reuse their builders. A property that $refs another component schema calls that schema's builder (anOwner(...), or anOwner.full(...) inside .full() within the depth above) with a variant drawn from the caller's seeded Faker, so nested values are deterministic yet array items are distinct from each other, and fixtures.ts stays linear in the number of schemas. A reference back into a cycle (a self-reference such as TreeNode.children, or A -> B -> A) is guarded: the recursion goes one nested level deep, then yields null, so every builder terminates.
  • Non-object schemas (enums, arrays, primitives) get a value builder without overrides: aTaskStatus(variant?) and aTaskStatus.full(variant?).
  • Values are best-effort. Generated values follow the same Faker rules as handlers.ts (see below); they are cast to the model type rather than proven against it, so put values a test depends on in overrides.

How bodies are generated

The resolver body for each handler is built from the operation's selected 2xx JSON response schema. Faker calls are chosen per schema node:

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: max_array_items }, () => item)
object with additionalPropertiesObject.fromEntries(...) with two generated entries
null / unresolvablenull

Non-obvious behavior

  • The seed lives in the output, not the runtime. faker.seed(<seed>) is the first executable line of the generated file. To change mock values, re-generate with a different seed. Two runs of the same spec and seed produce byte-identical handlers.
  • Handler URLs are origin-anchored. A bare relative matcher such as '/api/v1/tasks' is resolved by MSW against location.href, so in Node (setupServer from msw/node) it never matches an absolute request like fetch('http://localhost/api/v1/tasks'). Generated handlers therefore always carry an origin: '*/api/v1/tasks' by default (MSW's wildcard: any origin, browser and Node alike), or '<base_url>/api/v1/tasks' when base_url is set. Set base_url when you want handlers to match only one origin, or when your server URL carries a base path the spec's paths do not include (for example "https://api.test/v2"). Output generated before this change used bare relative paths; re-generate to pick up the fix.
  • 2xx selection order. The generator prefers a 200 response, then 201, then the first 2xx code that declares an application/json body. A 200 emits HttpResponse.json(body) with no status argument; any other 2xx (for example 201) emits HttpResponse.json(body, { status: N }).
  • No JSON body. If the chosen 2xx response has no application/json schema, the handler 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, text/csv, application/octet-stream, image, and so on) the handler emits new HttpResponse(null, { status: N }) rather than falsely claiming application/json. Only application/json content is mocked with a faker body.
  • allOf is merged; anyOf / oneOf is not unioned. Properties of all allOf members are merged into one object and their required arrays concatenated. For anyOf / oneOf the generator picks the first concrete (non-$ref) member, or resolves the first member if all are refs.
  • depth_cap bounds recursion. The depth counter increments only on $ref resolution and on allOf / anyOf / oneOf resolution steps. Array nesting and plain property access do not count. When depth exceeds depth_cap, the node 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.
  • Type unions and missing types. For an OpenAPI 3.1 type array, the generator uses the first element. A schema with no type but with properties is treated as an object; otherwise it emits null.
  • Path-rooted guard rails. input_openapi and output paths that resolve into system directories (/etc, /usr, /bin, C:\Windows, and similar) are rejected as likely misconfiguration. This is a guard rail, not a security sandbox.

Programmatic API

The package also exports a programmatic surface for building tooling around it:

import {
  generateHandlers,
  generateFixtures,
  generate,
  loadConfig,
  loadConfigs,
} from '@codewithagents/openapi-msw'

// Build the handlers file in memory from a parsed spec.
const { filename, content } = generateHandlers(spec, {
  seed: 42,
  maxArrayItems: 3,
  depthCap: 30,
  baseUrl: 'https://api.test', // optional; omit for the '*' wildcard origin
})
// filename === 'handlers.ts'

// Build fixtures.ts in memory.
const fixtures = generateFixtures(spec, {
  seed: 42,
  maxArrayItems: 3,
  depthCap: 30,
  modelsImport: './models.js',
})
// fixtures.filename === 'fixtures.ts'

generate runs the full config-driven flow (load config, parse spec, write files). loadConfig / loadConfigs read and validate a config file. The exported types are MswConfig, HandlerGenOptions, FixtureGenOptions, and GeneratedFile.


Part of the openapi-zod-ts suite

openapi-msw is one of five published packages in the codewithagents/openapi-zod-ts monorepo. Four generators emit files from a single OpenAPI spec, and one runtime helper maps API errors in app code:

PackageRole
openapi-zod-tsGenerator: TypeScript models, a native fetch client, and Zod schemas
@codewithagents/openapi-serverGenerator: framework-agnostic service interface plus an optional hono | express | fastify | none router (default none)
@codewithagents/openapi-react-queryGenerator: typed React Query v5 hooks over the generated client
@codewithagents/openapi-mswGenerator: this package, MSW v2 handlers with seeded Faker mock data
@codewithagents/api-errorsRuntime helper: map backend error responses to form-field errors

See the full-stack reference app (Fastify + React + react-query, with a browser e2e suite) for these packages working together end to end.


License

MIT

版本与发布记录

@codewithagents/openapi-msw 共发布 5 个版本,首次发布于 2026/06/06;过去 12 个月发布了 5 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.3.0latest2026/11/036 天前
0.2.22026/11/037 天前
0.2.12026/06/224 个月前
0.2.02026/06/114 个月前
0.1.02026/06/065 个月前

关于 @codewithagents/openapi-msw 的常见问题

@codewithagents/openapi-msw 是什么?

Generate MSW v2 handlers with seeded Faker mock data from OpenAPI 3.1 specs

如何安装 @codewithagents/openapi-msw?

在项目目录运行 npm install @codewithagents/openapi-msw,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:msw(^2.0.0 || ^3.0.0)、@faker-js/faker(^10.5.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。它还提供命令行工具 openapi-msw,可以用 npx openapi-msw 直接运行。

@codewithagents/openapi-msw 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@codewithagents/openapi-msw 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@codewithagents/openapi-msw 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.19.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@codewithagents/openapi-msw 可以商用吗?

@codewithagents/openapi-msw 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@codewithagents/openapi-msw 的下载量有多少?

根据 npm 官方下载统计,@codewithagents/openapi-msw 在 2026/10/10 – 2026/11/08 这 30 天内被下载 3,226 次,日均约 108 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@codewithagents/openapi-msw 还在维护吗?

最新版本 0.3.0 发布于 2026/11/03(6 天前);至今共发布 5 个版本,其中过去 12 个月发布了 5 个;首次发布于 2026/06/06。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。