A mock server based on json-server
npm install @36node/mock-server[![version][0]][1] [![downloads][2]][3]
mock-server 基于 json-server, 为了更好的提供数据 mock 服务.
``bash`
$ yarn install @36node/mock-server
`js
#!/usr/bin/env node
const mockServer = require("@36node/mock-server");
const app = mockServer({
db: {
pets: [
{ id: 1, name: "kitty", tag: "CAT", grade: 3 },
{ id: 2, name: "pi", tag: "DOG", grade: 4 },
],
},
rewrites: {
"/store/pets*": "/pets$1",
},
routers: [], // custom middle ware
aggregations: {
"/pets": {
grade: records => _.sumBy(records, "grade") / records.length,
count: records => records.length,
},
},
});
app.listen(3000, () => {
console.log("JSON Server is running on port 3000");
});
`
使用 react-app-rewired 时, 通过 config-overwrites.js 文件 配置 devServer
`js
const stopMock = process.env.MOCK === "false" || process.env.MOCK === "FALSE";
const defaultServerOpts = { delay: 500 };
const {
serverOpts = defaultServerOpts,
db: {
pets: [
{ id: 1, name: "kitty", tag: "CAT", grade: 3 },
{ id: 2, name: "pi", tag: "DOG", grade: 4 },
],
},
rewrites: {
"/store/pets*": "/pets$1",
},
routers: [], // custom middle ware
aggregations: {
"/pets": {
grade: records => _.sumBy(records, "grade") / records.length,
count: records => records.length,
},
},
} = someMockConfig;
const mockServer = require("@36node/mock-server");
module.exports = {
...otherConfig,
devServer: function(configFunction) {
return function(proxy, allowedHost) {
const config = configFunction(proxy, allowedHost);
if (stopMock) {
return config;
}
/**
* mock server hoc
* @param {Express.Application} app
*/
function configMock(app) {
// 根据 请求的 header.accept 的类型决定是正常渲染,还是进入mock-server
const shouldMockReq = req => {
return (
req.method !== "GET" ||
(req.headers.accept &&
req.headers.accept.indexOf("application/json") !== -1)
);
};
if (serverOpts.delay) {
app.use((req, res, next) => {
if (shouldMockReq(req)) {
return pause(serverOpts.delay)(req, res, next);
}
return next();
});
}
mockServer({ app, db, rewrites, routers, shouldMockReq });
return app;
}
const prev = config.before;
config.before = compose(
configMock,
app => {
prev(app);
return app;
}
);
return config;
};
},
};
`
mockServer(opts)
params:
1. opts: Object
db: // 同 json-server 的 db 配置 https://github.com/typicode/json-server#getting-started
rewrites (Optional): 同 json-server https://github.com/typicode/json-server#rewriter-example
routes (Optional): [Express.Middleware] 同 json-server custom-middle https://github.com/typicode/json-server#add-middlewares
aggregations (Optional): Object 见下文 Aggregation
app (Optional): Express.Application, 如果没有则自动新建
shouldMock (Optional): (req, res) => Boolean, 判断 request 是否使用 mock-server 的 中间件
返回:Express.Appliction
使用标准 url query 格式传递数组数据
`curl`
a=1&a=2
Use . to access deep properties
`curl`
GET /posts?title=json-server&author=typicode
GET /posts?id=1&id=2
GET /comments?author.name=typicode
Use _offset and optionally _limit to paginate returned data. (an X-Total-Count header is included in the response)
In the Link header you'll get first, prev, next and last links.
`curl`
GET /posts?_offset=10
GET /posts?_offset=7&_limit=20
note: _10 items are returned by default_
Add _sort and _order (ascending order by default)
`curlasc
GET /posts?_sort=views
note: _list posts by views ascending order and comments by votes descending order_
For multiple fields, use the following format:
`curl
GET /posts/1/comments?_sort=-votes&_sort=likes
`\_prefixing a path with
- will flag that sort is descending order.
When a path does not have the - prefix, it is ascending order.Operators
Add
_gt, _lt, _gte or _lte for getting a range`curl
GET /posts?views_gte=10&views_lte=20
`Add
_ne to exclude a value`curl
GET /posts?id_ne=1
`Add
_like to filter (RegExp supported)_like support array`curl
GET /posts?title_like=server
`Select
Specifies which document fields to include or exclude
`curl
GET /posts?_select=title&_select=body
GET /posts?_select=-comments&_select=-views
`or
`curl
_select=title,body
`_prefixing a path with
- will flag that path as excluded._
_When a path does not have the - prefix, it is included_
_A projection must be either inclusive or exclusive._
_In other words, you must either list the fields to include (which excludes all others),_
_or list the fields to exclude (which implies all other fields are included)._Aggregation
聚合的 query 请求。聚合请求通过 \_group 和 \_select 参数来控制,通过 opts.aggregations 配置:
比如对于一个 db 配置:
`js
const faker = require("faker");
const _ = require("lodash");
const moment = require("moment");const now = moment();
const generate = count =>
_.range(count).map((val, index) => {
const birthAt = faker.date.between(
moment()
.subtract(10, "year")
.toDate(),
moment()
.subtract(1, "year")
.toDate()
);
const age = now.diff(moment(birthAt), "year");
return {
id: faker.random.uuid(), // pet id
name: faker.name.lastName(), // pet name
tag: faker.random.arrayElement(["CAT", "DOG"]), // pet tag
owner: faker.name.firstName(), // pet owner
grade: faker.random.number({ min: 1, max: 5 }), // pet grade
age, // pet age
birthAt: birthAt.toISOString(), // pet birth time
};
});
const db = {
pets: generate(100),
};
`其中包括了 100 个 pets 的 mock 数据,可使用的路由有:
`
GET /pets
GET /pets/{petId}
POST /pets
PUT /pets/{petId}
PATCH /pets/{petId}
DELETE /pets/{petId}
`聚合只在
GET /pets 中有效$3
如果需要统计 pets 中猫和狗的数量, 可以对 tag 分组
配置 aggregations 参数
`js
aggregations: {
"/pets": {
// records 是分组后的数据集合
count: records => records.length,
// 默认支持 两种聚合简写 求和 'sum' 和 平均 ‘avg'
grade: 'avg',
},
},
`请求:
`
GET /pets?_group=tag
`结果:
`json
[
{
"id": "tag=CAT",
"tag": "CAT",
"grade": 3.017857142857143,
"count": 56
},
{
"id": "tag=DOG",
"tag": "DOG",
"grade": 3.3863636363636362,
"count": 44
}
]
`$3
如果需要统计每个月分别生了多少猫和狗, 可以按 tag 和 birthAt.month 分组
对于时间的分组条件,可以采用不同粒度进行分组,query 的格式为
birthAt.month 表示在 birthAt 字段上 按照 月粒度进行分组。支持的粒度包括:
`js
[
"year", // 年
"quarter", // 季度
"month", // 月
"week", // 星期
"isoWeek", // iso 星期
"day", // 天
"hour", // 小时
"min", // 分钟
"second", // 秒
];
`请求:
`
GET /pets?_group=tag&_group=birthAt.month
`结果:
`json
[
...,
{
"id": "tag=CAT&birthAt=2012-12-31T16%3A00%3A00.000Z",
"tag": "CAT",
"birthAt": "2012-12-31T16:00:00.000Z",
"count": 6
},
{
"id": "tag=DOG&birthAt=2014-12-31T16%3A00%3A00.000Z",
"tag": "DOG",
"birthAt": "2014-12-31T16:00:00.000Z",
"count": 7
}
]
`Tips:
1. 在返回结果中,birthAt 当前月的起始时间(UTC),如果使用其他粒度,则类似。
2. 如果同时传入统一字段的多个时间粒度,比如
_group=birthAt.year&_group=birthAt.month`, 则较小的时间粒度(month)会生效.[0]: https://img.shields.io/npm/v/@36node/mock-server.svg?style=flat
[1]: https://npmjs.com/package/@36node/mock-server
[2]: https://img.shields.io/npm/dm/@36node/mock-server.svg?style=flat
[3]: https://npmjs.com/package/@36node/mock-server