Programmable fake APIs server.
- Requirements
- Installation
- Usage
- Endpoint Configuration
- Params interceptors
- Response transformers
-
transformJSON
-transformText
- Development Mode
- Contributing
- Credits
- License
npm install mokd --save
const { Server } = require('mokd');
const server = new Server({
baseUrl: '', //optional
endpoints: [
{
path: '/user/',
response: {
id: 1
name: 'John Doe'
}
//... more endpoints configuration here, see below
}
]
});
const { data } = server.resolve({ url: '/user/' });
//data === { "id": 1, "name": "John Doe" }
In order to use the server with an existing connect/express application you need to use the built-in adapter.
const express = require('express');
const { Server, connectServer } = require('mokd');
const server = new Server({
baseUrl: '', //optional
endpoints: [
//... endpoints configuration object here, see below
]
});
const app = express();
app.use(connectServer(server));
app.listen(8000);
In order to use the server with an existing koa application you need to use the built-in adapter.
const Koa = require('koa');
const { Server, koaServer } = require('mokd');
const server = new Server({
baseUrl: '', //optional
endpoints: [
//... endpoints configuration object here, see below
]
});
const app = new Koa();
app.use(koaServer(server));
app.listen(8000);
If you're using another application framework, you can create a custom adapter. Refer to the built-in koa or connect for example implementations.
The core of the server is the endpoints
configuration option.
You can provide either an array of endpoints or a function returning an array of endpoints. In the latter case the function receives the server instance as first argument.
const { Server } = require('mokd');
//endpoints as array
const server = new Server({
endpoints: [
//... endpoints configuration object here, see below
]
});
const otherServer = new Server({
endpoints: (srv) => [
// you can access instance configuration here via `srv.options`
//... endpoints configuration object here, see below
]
});
Endpoints are objects with the following properties:
method
(string
): The expected methods of the incoming request (default:GET
),path
(string|regexp|function
): the path to match relative to the root URL. If a function it must return a string or a regular expression (default:null
),delay
(number|function
): force a delay in milliseconds for the response. If a function it must return a number (default:0
),contentType
(string
): Response content type (default:application/json
),response
(*|function
): Response body template. Could be any type of content in relation to theContentType
parameter. If a function it will be executed with aparams
object and theendpoint
itself as arguments. (default:null
)
The params
object contains 3 properties:
$req
: the original request object$parsedUrl
: The request URL parsed by NodeJS nativeurl.parse
method$routeMatch
: either an object (whenpath
is a string) or an array of matched segments (whenpath
is a regular expression). See below for details.
Endpoint's path
configuration could be a plain string, a regular expression or a string with Express-like parameters (see path-to-regexp for details).
$routeMatch
format will vary based on the provided path
:
- regular expression:
$routeMatch
will be the resulting array of callingRegExp.prototype.exec
on it - string or Express-like route:
$routeMatch
will be and object with named parameters as keys. Note that even numeric parameters will be strings.
Examples:
/* Plain string */
{
path: '/api/v1/users'
// /api/v1/users/ -> $routeMatch === {}
}
/* Express-like path */
{
path: '/api/v1/users/:id'
// /api/v1/users/10 -> $routeMatch === {id: '10'}
}
/* RegExp */
{
path: /^\/api\/v1\/users\/(\d+)$/
// /api/v1/users/10 -> $routeMatch === ['/api/v1/users/10', '10']
}
Any key in the response template could be either a plain value or a function. If a function, it will be executed at response time with a params
object and the endpoint
itself as arguments.
The baseUrl
configuration option sets up a base URL for every relative endpoint path provided. To override the base URL use absolute URLs.
Note: baseUrl
applies just to string paths.
const server = new Server({
baseUrl: '/api/v1/', //optional
endpoints: [
{
// this endpoint will respond at /api/v1/users
path: 'users',
response: {
// ...
}
}, {
// this endpoint will respond at /custom/path
path: '/custom/path',
response: {
// ...
}
}
]
});
- A basic GET endpoint returning a JSON object
const endpoint = {
path: '/api/v1/user',
response: {
name: 'John',
surname: 'Doe'
}
};
- A GET endpoint returning dynamic data provided by Chance
const chance = require('chance').Chance();
const endpoint = {
path: '/api/v1/user',
response: {
name: () => chance.first(),
surname: () => chance.last()
}
};
- A GET endpoint matching a regexp and returning a dynamic property based on the match
const chance = require('chance').Chance();
const endpoint = {
//matches either a male of female user request
path: /\/api\/v1\/user\/(male|female)$/,
response: {
name: (params) => chance.first({
gender: params.$routeMatch[1]
}),
surname: (params) => chance.last({
gender: params.$routeMatch[1]
})
}
};
- A GET endpoint matching a regexp and returning a dynamic response based on the match
const chance = require('chance').Chance();
const endpoint = {
path: '/api/v1/:frag',
response: (params) => {
//calling /api/v1/user
//params.$routeMatch === {'frag': 'user'}
if (params.$routeMatch.frag === 'user') {
return {
name: () => chance.first(),
surname: () => chance.last()
};
}
return {
error: 'Not matching anything'
};
}
};
- A POST endpoint, accepting a body request and returning a success message
Note: to parse the request body you usually need to enable body parsing in your application (in express / connect you can use body-parser).
const endpoint = {
path: '/api/v1/send',
method: 'POST',
response: (params) => {
if (!params.$req.body.username || !params.$req.body.password) {
return {
success: false,
msg: 'You must provide a username and a password'
};
}
return {
success: true,
msg: 'Succesfully logged in!'
};
}
};
The params object is automatically generated by the server. Anyway you can manipulate it by providing an array of interceptor functions as the interceptors
key of the server configuration.
Every interceptor function receives the params object filtered by the previous interceptor.
const addCustomKey = (params) => Object.assign(params, { keyID: 'my-custom-id' });
const addIfKey = (params) => Object.assign(params, params.keyID && { someData: '...' });
const server = new Server({
interceptors: [
addCustomKey,
addIfKey
]
});
You can instruct the server on which data format it has to provide for each endpoint's contentType
with response transformers.
Response transformers are functions that take in 3 arguments and return formatted data (usually stringified in order the be a valid server response).
The arguments are:
data
: raw data object generated by theresponse
property of the endpoint. Note that at this point dynamic response fields have not been yet been processedendpoint
: matched endpoint for the requestparams
: param object as used in theresponse
property of the endpoint
An array of transform functions can be set as a transformers
key in the Server
configuration. The server will iterate on every transformer until it encounters one that doesn't return undefined
.
By default the server comes with two built-in transformers:
Resolves any dynamic response template key and returns a stringified JSON object.
It's an high order function that takes a JSON stringifier function as first argument (defaults to JSON.stringify
).
const { Server } = require('mokd');
const { transformJSON } = require('mokd/lib/utils');
cost server = new Server({
endpoints: [ ... ],
transformers: [
transformJSON()
]
});
Returns a string representation of the data provided by the endpoint's response.
const { Server } = require('mokd');
const { transformText } = require('mokd/lib/utils');
cost server = new Server({
endpoints: [ ... ],
transformers: [
transformText
]
});
Mocked data could change frequently during development. Instead or restarting your application, you can instantiate a watcher that will listen for file changes and reload endpoints automatically.
In order for the watcher to work correctly you need to move the endpoints configuration to its own file and pass its path to the watcher.
// endpoints.js
module.exports = [{
path: 'api/users/'
response {
// ...
}
}];
// server.js
const { createWatcher, Server } = require('../index');
const server = new Server(); // <-- don't pass endpoints here
const watcher = createWatcher({
server,
entrypoint: './endpoints.js'
paths: ['./mocks/**/*.*'] //additional paths to watch
});
watcher.start();
createWatcher
config object as the following options:
server
: A mock server instancecwd
: (default:process.cwd()
) The base directory from which relative paths are resolved.entrypoint
: A path to a file exposing a list of endpoints. Either absolute or relative tocwd
paths
: A list of files or patterns to be watched for changes (Seechokidar.watch
path
argument for details).watchOptions
:chokidar.watch
options. By defaultignoreInitial
istrue
andcwd
has the same value as thecwd
option here.
createWatcher
returned object has the following methods:
start
: runs the watcher.close
: proxy to chockidar'sclose
methodon
: proxy to chockidar'son
methodupdate(clear = true)
: Loads a fresh copy of theentrypoint
file and every watched file. To reload just the entrypoint setclear
tofalse
.clearCache
: removes watched files from NodeJS module's cache.
- Fork it or clone the repo
- Install dependencies
npm install
- Run
npm start
to launch a development server - Code your changes and write new tests in the
tests
folder. - Ensure everything is fine by running
npm test
andnpm run eslint
- Push it or submit a pull request :D
Created by Marco Solazzi