vite-plugin-mock-dev-server
Vite Plugin for API mock dev server.
English | ็ฎไฝไธญๆ
Features
- โก๏ธ Lightweight, Flexible, Fast.
- ๐งฒ Not injection-based, non-intrusive to client code.
- ๐ก Pure ESModule.
- ๐ฆพ Typescript.
- ๐ฅ HMR
- ๐ท Support
.[cm]?js/.ts/.json/.json5. - ๐ฆ Auto import mock file.
- ๐จ Support any lib, like
mockjs, or do not use it. - ๐ฅ Path rule matching, request parameter matching.
- โ๏ธ Support Enabled/Disabled any one of the API mock.
- ๐ Supports response body content type such as
text/json/buffer/stream. - โ๏ธ Use
server.proxy - ๐ Support
viteConfig.defineandenvin the mock file. - โ๏ธ Support
viteConfig.resolve.aliasin the mock file. - ๐ Support
vite previewmode. - ๐ค Support
multipartcontent-type, mock upload file. - ๐ฅ Support mock download file.
- โ๏ธ Support
WebSocket MockandServer-Sent Events Mock - ๐ Support recording and replay requests
- ๐ Support building small independent deployable mock services.
Documentation
See the documentation for more details.
[!IMPORTANT]
The plugin no longer supportsCommonJSimports. Please useESModuleto import the plugin.
[!IMPORTANT]
The current document is for thev2version of the plugin. If you are using thev1version, please refer to the Migration Guide.
Install
# npm
npm i -D vite-plugin-mock-dev-server
yarn
yarn add vite-plugin-mock-dev-server
pnpm
pnpm add -D vite-plugin-mock-dev-server
Usage
vite.config.ts
import { defineConfig } from 'vite'
import { mockDevServerPlugin } from 'vite-plugin-mock-dev-server'
export default defineConfig({
plugins: [mockDevServerPlugin(/ plugin options /)],
// The fields defined here can also be used in mock.
define: {},
server: {
// plugin will read server.proxy
proxy: {
'^/api': { target: 'http://example.com' },
},
},
})
The plugin will read the configuration of server.proxy or options.prefix, and enable mock matching for matched URLs.
The plugin will also read the define configuration, which supports direct use in mock files.
Edit Mock File
By default, write mock data in the mock directory of your project's root directory:
mock/*/.mock.ts :
import { defineMock } from 'vite-plugin-mock-dev-server'
export default defineMock({
url: '/api/user/:id',
body: { a: 1, b: 2 },
})
Methods
mockDevServerPlugin(pluginOptions)
vite plugin
vite.config.ts
import { defineConfig } from 'vite'
import { mockDevServerPlugin } from 'vite-plugin-mock-dev-server'
export default defineConfig({
plugins: [mockDevServerPlugin({/ plugin options /})],
})
defineMock(mockOptions)
Mock Options Type Helper
import { defineMock } from 'vite-plugin-mock-dev-server'
export default defineMock({
url: '/api/test',
body: {},
})
createDefineMock(transformer)
Return a custom defineMock function to support preprocessing of mock config.
import path from 'node:path'
import { createDefineMock } from 'vite-plugin-mock-dev-server'
// Preprocessed mock url
const defineAPIMock = createDefineMock((mock) => {
mock.url = path.join('/api', mock.url)
})
export default defineApiMock({
url: '/test', // Complete as '/api/test'
})
createSSEStream(req, res)
Create a Server-sent events write stream to support mocking EventSource.
import { createSSEStream, defineMock } from 'vite-plugin-mock-dev-server'
export default defineMock({
url: '/api/sse',
response: (req, res) => {
const sse = createSSEStream(req, res)
sse.write({ event: 'message', data: { message: 'hello world' } })
sse.end()
},
})
Plugin Options
enabled
- Type:
boolean - Default:
true - Details:
false, the plugin will not work.
prefix
- Type:
string | string[] - Default:
[] - Details:
prefix will be proxied to the corresponding target. If the prefix value starts with ^, it will be recognized as a RegExp.
> In general, server.proxy is sufficient to meet the needs. Adding this item is for compatibility with certain scenarios.
wsPrefix
- Type:
string | string[] - Default:
[] - Details:
wsPrefix and using the ws/wss protocol will be proxied to the corresponding target.
If the value of wsPrefix starts with ^, it will be recognized as a RegExp.
> Different from using viteConfig.server.proxy by default for http mock, websocket mock does not use the ws-related configuration in viteConfig.server.proxy. Also, rules configured in wsPrefix cannot be configured simultaneously in viteConfig.server.proxy, as it will cause conflicts when starting the vite server because multiple instances of WebSocketServer cannot be implemented for the same request.
> This conflict is neither a problem with Vite nor with the plugin; it belongs to a reasonable error type. When switching between WebSocket Mock and WebSocket Proxy, please pay attention to avoid duplicate configurations that may cause conflicts.
cwd
- Type:
string - Default:
process.cwd() - Details:
include and exclude.
dir
- Type:
string - Default:
'mock' - Details:
include
- Type:
string | string[] - Default๏ผ
['*/.mock.{js,ts,cjs,mjs,json,json5}'] - Details:
exclude
- Type:
string | string[] - Default๏ผ
['/node_modules/'] - Details:
reload
- Type:
boolean - Default:
false - Details:
activeScene
- Type:
string | string[] - Default:
'' - Details:
Only mocks whose scene intersects with this value (or have no scene configured) will be considered for matching. Can be overridden per-request via the X-Mock-Scene header.
cors
- Type:
boolean | CorsOptions - Default:
true - Details:
Configure to cors, see cors
log
- Type:
boolean | 'info' | 'warn' | 'error' | 'silent' | 'debug' - Default:
info - Details:
formidableOptions
- Type:
formidable.Options - Details:
formidable, see formidable options
example: Configure to file upload dir
MockDevServerPlugin({
formidableOptions: {
uploadDir: path.join(process.cwd(), 'uploads'),
},
})
cookiesOptions
- Type:
CookiesOptions - Details:
cookies, see cookies
bodyParserOptions
- Type:
BodyParserOptions - Details:
co-body, see co-body
build
- Type:
boolean | ServerBuildOptions - Default:
false - Details:
interface ServerBuildOptions {
/**
* server port
* @default 8080
*/
serverPort?: number
/**
* build output dir
* @default 'mockServer'
*/
dist?: string
/**
* log level
* @default 'error'
*/
log?: LogLevel
}
record
- Type:
false | RecordOptions - Default:
false - Details๏ผ
Based on vite.server.proxy, the plugin records request data proxied by http-proxy.
After receiving a response, the plugin will record the request data and response data to the specified directory.
ts
* // Record all requests
* filter: (req) => true
* // Record requests using glob pattern
filter: { mode: 'glob', include: '/api/*' }
* // Record requests using path-to-regexp pattern
* filter: { mode: 'path-to-regexp', include: '/api/:id' }
interface RecordOptions {
/**
* Whether to enable the record feature
* - true: Enable, automatically record proxy responses
* - false: Disable (default)
* @default false
*/
enabled?: boolean
/**
* Filter requests to record
* - Function: Custom filter function, return true to record
* - Object: Include/exclude patterns with glob or path-to-regexp mode
* @example
*
/
filter?:
| ((req: RecordedReq) => boolean)
| {
/**
* Include the request links that need to be recorded
*
* String: Glob pattern or path-to-regexp pattern
* (Use the mode option to set the mode, default is glob)
*/
include?: string | string[]
/**
* Exclude request links that do not need to be recorded
*
* String: Glob pattern or path-to-regexp pattern
* (Use the mode option to set the mode, default is glob)
*/
exclude?: string | string[]
/**
* Matching mode for include/exclude patterns
* - 'glob': Glob pattern matching (default)
* - 'path-to-regexp': Path-to-regexp pattern matching
*/
mode: 'glob' | 'path-to-regexp'
}
/** * Directory to store recorded data * Relative to project root * @default 'mock/.recordings' */ dir?: string /** * Whether to overwrite existing recorded data * - true: Overwrite old data for the same request (default) * - false: Keep old data, do not record new data * @default true */ overwrite?: boolean /** * Expiration time for recorded data in seconds * - 0: Never expire (default) * - Positive number: Expire after specified seconds * @default 0 */ expires?: number /** * Status codes to record * - Empty array: Record all status codes (default) * - Specify one or more status codes to filter * @default [] */ status?: number | number[] /** * Should a .gitignore be added to the recording directory * - true: Add (default) * - false: Do not add * @default true */ gitignore?: boolean }
replay
- Type:
boolean - Default:
false - Details๏ผ
Replay works independently of recording, replay: true can be used with record: { enabled: false } to reuse existing recordings. A warning is printed at startup when recording is disabled.
priority
- Type:
MockMatchPriority - Details:
Mock Options
http mock
import { defineMock } from 'vite-plugin-mock-dev-server'
export default defineMock({
url: '/api/test',
body: { message: 'hello world' },
})
websocket mock
import { defineMock } from 'vite-plugin-mock-dev-server'
export default defineMock({
url: '/socket.io',
ws: true,
setup(wss) {
wss.on('connection', (ws, req) => {
console.log('connected')
})
},
})
options.url
- Type:
string - Details:
options.enabled
- Type:
boolean - Default:
true - Details:
options.scene
- Type:
string | string[] - Default:
'' - Details:
When not configured, the mock is universal and always matches regardless of active scenario.When configured, the mock only matches when at least one of its scenarios matches one of the active scenarios.
options.method
- Type:
Method | Method[]
type Method = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH'
- Default:
['GET', 'POST'] - Details:
options.type
- Type:
'text' | 'json' | 'buffer' | string - Details:
When the response body returns a file and you are not sure which type to use,
you can pass the file name as the value. The plugin will internally search for matching
content-type based on the file name suffix.
options.headers
- Type:
object | (request: MockRequest) => object | Promise - Default:
{ 'Content-Type': 'application/json' } - Details:
options.status
- Type:
number - Default:
200 - Details:
options.statusText
- Type:
string - Default:
"OK" - Details:
options.delay
- Type:
number | [number, number] - Default:
0 - Details:
unit: ms
options.body
- Type:
Body | (request: MockRequest) => Body | Promise
type Body = string | object | Buffer | Readable
- Details:
body takes precedence over response.
options.response
- Type:
(req: MockRequest, res: MockResponse, next: (err?: any) => void) => void | Promise - Details:
req, you can also get parsed request information such as
query, params, body and refererQuery.
options.cookies
- Type:
CookiesOptions | (request: MockRequest) => CookiesOptions | Promise
type CookiesOptions = Record<string, CookieValue>
type CookieValue = string | [string, SetCookie]
- Details:
options.validator
- Type:
Validator | (request: MockRequest) => boolean
interface Validator {
/**
* The query string located after ? in the request address has been parsed into JSON.
*/
query: Record<string, any>
/**
* The queryString located after ? in the referer request has been parsed as JSON.
*/
refererQuery: Record<string, any>
/**
* Body data in the request
*/
body: Record<string, any>
/**
* The params parameter parsed from the /api/id/:id in the request address.
*/
params: Record<string, any>
/**
* headers data in the request
*/
headers: Headers
}
- Details:
Sometimes, for the same API request, data needs to be returned based on different request parameters.
However, if all of this is written in a single mock's body or response, the content can become cumbersome and difficult to manage. The function of a validator allows you to configure multiple mocks with the same URL simultaneously and determine which mock should be used through validation.
options.ws
- Type:
boolean - Default:
false - Details:
options.setup
- Type:
(wss: WebSocketServer, ctx: WebSocketSetupContext) => void - Details:
interface WebSocketSetupContext {
/**
* When defining WSS, you may perform some automatic or looping tasks.
* However, when hot updating, the plugin will re-execute setup(),
* which may result in duplicate registration of listening events and looping tasks
* such as setTimeout. You can use onCleanup() to clear these automatic or looping tasks.
*/
onCleanup: (cleanup: () => void) => void
}
options.error
- Type:
MockErrorConfig | undefined - Details:
interface MockErrorConfig {
/**
* Error probability (0-1), default is 0.5
* @default 0.5
*/
probability?: number
/**
* Error status code, default is 500
* @default 500
*/
status?: number
/**
* Error status text
*/
statusText?: string
/**
* Custom error response body, suitable for when the status is 200, but the response body needs to simulate an error scenario
* @example
* { code: 500, msg: 'Internal Server Error', result: null }
*/
body?: ResponseBody | ResponseBodyFn
}
Request/Response Enhance
When defining methods using headers, body, and response, the plugin adds new content to the request and response parameters.
In Request:
The original type of request is http.IncomingMessage. The plugin adds data such as query, params, body, refererQuery, and the getCookie(name) method for obtaining cookie information on this basis.
type Request = http.IncomingMessage & {
query: object
params: object
body: any
refererQuery: object
getCookie: (name: string, option?: Cookies.GetOption) => string | undefined
}
In Response:
The original type of response is http.ServerResponse. The plugin adds setCookie(name, value) method for configuration cookies on this basis.
type Response = http.ServerResponse<http.IncomingMessage> & {
setCookie: (name: string, value?: string | null, option?: Cookies.SetOption) => void
}
Share Mock Data
Due to each mock file being compiled as a separate entry point, the local files they depend on are also compiled within. Additionally, each mock file has an independent scope. This means that even if multiple mock files collectively depend on a data.ts file, they cannot share data. If one mock file modifies the data in data.ts, other mock files will not receive the updated data.
To address this, the plugin offers a defineMockData function, which allows using data.ts as a shared data source within mock files.
type defineMockData<T> = (
key: string, // key
initialData: T, // initial data
options?: {
persistOnHMR?: boolean // persist the data value on HMR
}, // options
) => [getter, setter] & { value: T }
Examples
data.ts
import { defineMockData } from 'vite-plugin-mock-dev-server'
export default defineMockData('posts', [
{ id: '1', title: 'title1', content: 'content1' },
{ id: '2', title: 'title2', content: 'content2' },
])
*.mock.ts
import { defineMock } from 'vite-plugin-mock-dev-server'
import posts from './data'
export default defineMock([
{
url: '/api/posts',
body: () => posts.value,
},
{
url: '/api/posts/delete/:id',
body: (params) => {
const id = params.id
posts.value = posts.value.filter((post) => post.id !== id)
return { success: true }
},
},
])
Tips๏ผ
> ThedefineMockDatafunction relies solely on the shared data support provided bymemory.
If persistent mock data is required, it is recommended to use anosqldatabase likelowdborlevel.
Custom-Path-Matching-Priority
Custom rules only affect links with dynamic parameters, such as: /api/user/:id
The priority of the path matching rules built into the plugin can already meet most needs, but if you need more flexible customization of the matching rule priority, you can use the priority parameter.
Exp๏ผ
import { defineConfig } from 'vite'
import mockPlugin from 'vite-plugin-mock-dev-server'
export default defineConfig({
plugins: [
mockPlugin({
priority: {
// The priority of matching rules is global.
// The rules declared in this option will take priority over the default rules.
// The higher the position of the rule in the array, the higher the priority.
global: ['/api/:a/b/c', '/api/a/:b/c', '/api/a/b/:c'],
// For some special cases where the priority of certain rules needs to be adjusted,
// this option can be used. For example, when a request matches both Rule A and Rule B,
// and Rule A has a higher priority than Rule B, but it is desired for Rule B to take effect.
special: {
// When both A and B or C match, and B or C is at the top of the sort order,
// insert A into the top position.
// The when option is used to further constrain the priority adjustment to
// be effective only for certain requests.
'/api/:a/:b/c': {
rules: ['/api/a/:b/:c', '/api/a/b/:c'],
when: ['/api/a/b/c'],
},
// If no when is specified, it means that all requests matching the rules need to have their priorities adjusted. It can be abbreviated as [key]: [...rules]
'/api/:a/b': ['/api/a/:b'],
},
},
}),
],
})
Tip:
> priority although it can adjust the priority,
most of the time you do not need to do so. For some special requests,
you can use static rules instead of priority,
as static rules always have the highest priority.
Examples
mock/*/.mock.{ts,js,mjs,cjs,json,json5}
See more examples๏ผ example
Match
/api/test, And returns a response body content with empty data
export default defineMock({
url: '/api/test',
})
Match
/api/test , And returns a static content data
export default defineMock({
url: '/api/test',
body: { a: 1 },
})
Only Support
GET Method
export default defineMock({
url: '/api/test',
method: 'GET',
})
In the response header, add a custom header and cookie
export default defineMock({
url: '/api/test',
headers: { 'X-Custom': '12345678' },
cookies: { 'my-cookie': '123456789' },
})
export default defineMock({
url: '/api/test',
headers({ query, body, params, headers }) {
return { 'X-Custom': query.custom }
},
cookies() {
return { 'my-cookie': '123456789' }
},
})
Define multiple mock requests for the same URL and match valid rules with validators
export default defineMock([
// Match /api/test?a=1
{
url: '/api/test',
validator: {
query: { a: 1 },
},
body: { message: 'query.a == 1' },
},
// Match /api/test?a=2
{
url: '/api/test',
validator: {
query: { a: 2 },
},
body: { message: 'query.a == 2' },
},
{
// ?a=3 will resolve to validator.query
url: '/api/test?a=3',
body: { message: 'query.a == 3' },
},
// Hitting the POST /api/test request, and in the request body,
// field a is an array that contains items with values of 1 and 2.
{
url: '/api/test',
method: ['POST'],
validator: { body: { a: [1, 2] } },
},
])
Response Delay
export default defineMock({
url: '/api/test',
delay: 6000, // delay 6 seconds
})
The interface request failed
export default defineMock({
url: '/api/test',
status: 502,
statusText: 'Bad Gateway',
})
Dynamic route matching
export default defineMock({
url: '/api/user/:userId',
body({ params }) {
return { userId: params.userId }
},
})
The userId in the route will be resolved into the request.params object.
Use the buffer to respond data
import { Buffer } from 'node:buffer'
// Since the default value of type is json,
// although buffer is used for body during transmission,
// the content-type is still json.
export default defineMock({
url: 'api/buffer',
body: Buffer.from(JSON.stringify({ a: 1 })),
})
// When the type is buffer, the content-type is application/octet-stream.
// The data passed in through body will be converted to a buffer.
export default defineMock({
url: 'api/buffer',
type: 'buffer',
// Convert using Buffer.from(body) for internal use
body: { a: 1 },
})
Response file type
Simulate file download, and pass in the file reading stream.
import { createReadStream } from 'node:fs'
export default defineMock({
url: '/api/download',
// When you are unsure of the type, you can pass in the file name for internal parsing by the plugin.
type: 'my-app.dmg',
body: () => createReadStream('./my-app.dmg'),
})
<a href="/api/download" download="my-app.dmg">Download File</a>
Use
mockjs
import Mock from 'mockjs'
export default defineMock({
url: '/api/test',
body: Mock.mock({
'list|1-10': [
{
'id|+1': 1,
},
],
}),
})
You need install mockjs
response to customize the response
export default defineMock({
url: '/api/test',
response(req, res, next) {
const { query, body, params, headers } = req
console.log(query, body, params, headers)
res.status = 200
res.setHeader('Content-Type', 'application/json')
res.end(
JSON.stringify({
query,
body,
params,
}),
)
},
})
Use json / json5
{
"url": "/api/test",
"body": {
"a": 1
}
}
multipart, upload files.
use formidable to support.
<form action="/api/upload" method="post" enctype="multipart/form-data">
<p>
<span>file: </span>
<input type="file" name="files" multiple="multiple" />
</p>
<p>
<span>name:</span>
<input type="text" name="name" value="mark" />
</p>
<p>
<input type="submit" value="submit" />
</p>
</form>
fields files mapping to formidable.File
export default defineMock({
url: '/api/upload',
method: 'POST',
body(req) {
const body = req.body
return {
name: body.name,
files: body.files.map((file: any) => file.originalFilename),
}
},
})
Graphql
import { buildSchema, graphql } from 'graphql'
const schema = buildSchema(
type Query {
hello: String
}
)
const rootValue = { hello: () => 'Hello world!' }
export default defineMock({
url: '/api/graphql',
method: 'POST',
body: async (request) => {
const source = request.body.source
const { data } = await graphql({ schema, rootValue, source })
return data
},
})
fetch('/api/graphql', {
method: 'POST',
body: JSON.stringify({ source: '{ hello }' }),
})
WebSocket Mock
// ws.mock.ts
export default defineMock({
url: '/socket.io',
ws: true,
setup(wss, { onCleanup }) {
const wsMap = new Map()
wss.on('connection', (ws, req) => {
const token = req.getCookie('token')
wsMap.set(token, ws)
ws.on('message', (raw) => {
const data = JSON.parse(String(raw))
if (data.type === 'ping') return
// Broadcast
for (const [_token, _ws] of wsMap.entires()) {
if (_token !== token) _ws.send(raw)
}
})
})
wss.on('error', (err) => {
console.error(err)
})
onCleanup(() => wsMap.clear())
},
})
// app.ts
const ws = new WebSocket('ws://localhost:5173/socket.io')
ws.addEventListener(
'open',
() => {
setInterval(() => {
// heartbeat
ws.send(JSON.stringify({ type: 'ping' }))
}, 1000)
},
{ once: true },
)
ws.addEventListener('message', (raw) => {
console.log(raw)
})
EventSource Mock
// sse.mock.ts
import { createSSEStream, defineMock } from 'vite-plugin-mock-dev-server'
export default defineMock({
url: '/api/sse',
response(req, res) {
const sse = createSSEStream(req, res)
let count = 0
const timer = setInterval(() => {
sse.write({
event: 'count',
data: { count: ++count },
})
if (count >= 10) {
sse.end()
clearInterval(timer)
}
}, 1000)
},
})
// app.js
const es = new EventSource('/api/sse')
es.addEventListener('count', (e) => {
console.log(e.data)
})
Mock Services
In some scenarios, it may be necessary to use the data provided by mock services for display purposes, but the project may have already been packaged, built and deployed without support from Vite and this plugin's mock service. Since this plugin supports importing various node modules in mock files at the design stage, the mock file cannot be inline into client build code.
To meet such scenarios, on one hand, the plugin provides support under vite preview, and on the other hand, it also builds a small independent mock service application that can be deployed to relevant environments during vite build. This can then be forwarded through other HTTP servers like Nginx to actual ports for mock support.
The default output is built into the directory dist/mockServer, generating files as follows:
./mockServer
โโโ index.js
โโโ mock-data.js
โโโ package.json
In this directory, execute npm install to install dependencies, and then execute npm start to start the mock server.
The default port is 8080.
You can access related mock interfaces through localhost:8080/.
Links
- vite
- awesome-vite
- rspack-plugin-mock - Rspack and Rsbuild plugin for API mock server
Contributing
Please read the Contributing Guide before contributing to this project.
Contributors
pengzhanbo ๐ ๐ค ๐ก ๐ป |
jiadesen ๐ค ๐ |
yogibaba ๐ป |
pfdgithub ๐ป ๐ |
chuyuan du ๐ป |
hlwen ๐ ๐ป |
weibt ๐ ๐ป |
Yuan-ZW ๐ |
YILS ๐ ๐ป |
jbleijenbergh ๐ |
LICENSE
The plugin is licensed under the MIT License