'moco' is a tool which allows mocking REST APIs with ease.
The idea of developing moco came from a time where I worked almost exclusively as frontend developer in an enviroment where the backend was extremely unstable and specifications changed from day to day. There was always the need of developing the client with no backend to test against, so with moco you can quickly define a new endpoint in the API and use it to help you develop the client.
Furthermore, it gives you the option to mask the "real" server, in other words, you can define a new endpoint for your existing API, set moco as your backend and moco will manage to pass through any other request that is not specifically directed to it, acting basically as a proxy.
Be aware that moco is only meant to be used in a development enviroment and right now it is not much more than a pet project, it covers my needs for my current enviroment, which is unencrypted HTTP/1.1. So expect some kind of problem if your enviroment has different conditions.
However if I feel it is useful, I will continue improving it and adding new features when I find the time, (feel free to open issues describing any kind of problem you may encounter).
nodejs v12.10.0, with any other version is untested
'moco' will use a series of defined endpoints to match the incoming requests and based on the user configuration, it will capture the request and return a mocked response or it will redirect the request to its intended destiny
Three fields are used to capture the request:
- Pathname: Required, only the pathname, the host is not needed and at the moment the query params are ignored
- Method: Required, the HTTP method, there is no much explanation needed
- Headers: Optional, two different things about a header can be checked to match a request. One, Is the header present? And two, if is in fact present, do its value match the expected one?
'moco' behaviour will be directed through a JSON configuration file, which is based in the following data model, but before diving in the model just a couple of considerations:
-
This model is not described in any standard notation (to my knowledge), is just a custom way of quickly expressing objects without too much nuisance
-
There are some symbols used that could be misinterpreted so here is its meaning
alias => type(regex)
represents a type alias, used for subsets of the primitive types?
means optional field(a | b)
it is just an or over the types
statusCode => integer(100-999)
headers {
string key1
string key2
...
string keyN
}
request {
string path
string method
headers headers?
}
response {
statusCode statusCode
headers headers?
(object | string) body?
}
endpoint {
request request
array(response) responses
}
config {
integer port
string maskedHost?
boolean isActive?
statusCode activeResponse?
array(endpoint) endpoints
}
Example of an actual configuration file (generated by moco, see how in the usage section):
{
"port": 8080,
"maskedHost": "example.com",
"endpoints": [
{
"request": {
"path": "path/to/{*}/endpoint",
"method": "GET",
"headers": {
"Accept": "*/*"
}
},
"responses": [
{
"statusCode": 200,
"headers": {
"Content-Type": "application/json",
"User-Agent": "moco/0.1"
},
"body": {
"message": "Success!"
}
},
{
"statusCode": 503,
"headers": {
"Content-Type": "application/json",
"User-Agent": "moco/0.1"
},
"body": {
"message": "Unavailable"
}
}
]
}
]
}
To capture the incoming request you only have to write the path and the method used by the request. But if there is some case that the path is parameterized there is something you can do to avoid using the exact same URL with every request.
For example, let's say that the path of interest is user/1/detail
. This path usually makes reference to an entity user identified by the id 1. To avoid having to remember ids or any kind of parameter, you could set the request to be captured with a path like the following, and moco will manage to match the incoming request
user/{*}/detail
where {*} can be any caracter in the unreserved group for URIs defined in RFC 3986. Just to clarify, the regex that should be honored is /[\w-.~]+/, and here is the excerpt of the aformentioned RFC talking about unreserverd characters
Create regexp to match incoming requests by path conforming
RFC 3986: https://www.ietf.org/rfc/rfc3986.txt
2.3. Unreserved Characters
Characters that are allowed in a URI but do not have a reserved
purpose are called unreserved. These include uppercase and lowercase
letters, decimal digits, hyphen, period, underscore, and tilde.
unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
The headers object is somewhat special, the usual way of expresing a set of headers should be something like an array (or whatever structure you feel like using) of objects similar to this
header {
string name
string value
}
But here, I am making use of the way in which javascript defines objects to ease the writing of the configuration file (and incidentally, sometimes, the code). Each one of the keys (key1, key2, ..., keyN) is a value in itself, the name of the header, and the value will be the actual value of header. An example of this can be seen in the previous example.
Another thing to notice is the treatment of the values in the headers object. If the value null
, moco will only check the existence of the header in the request, but if the value is a string, moco will check if the string defined in the configuration file is contained in the header of the incoming request.
Regarding HTTP status codes, the same condition as nodejs is enforced. In nodejs documentation, an invalid HTTP status code is defined as follows
ERR_HTTP_INVALID_STATUS_CODE: Status code was outside the regular status code range (100-999).
(Ref: https://nodejs.org/dist/latest-v12.x/docs/api/errors.html#errors_err_http_invalid_status_code)
As specified in the data model isActive
and activeResponse
are optional, if not present 'moco' will fill up its values for you. By default isActive
will be set to true
and activeResponse
will be set to the status code of the first response defined in the configuration file.
'moco' will print a warning to its console but it will allow it. The returned response will be empty, no headers and no body.
usage: node moco.js [help | run <path> | template [<path>]]
or making moco.js exectuable
usage: node <path_to_moco.js> [help | run <path> | template [<path>]]
-
help
Prints all available options to run moco the output will be like this:
usage: node moco.js [help | run <path> | template [<path>]] help Print this message run <path>, <path> : string Run moco using configuration file stored in <path> template [<path>], <path> : string Convenience command, saves a configuration template file to <path>. If <path> not specified `config.moco.json` will be created in the current directory
-
template
Creates a configuration file with the expected format which can be easilied tailored to any API. It will be saved to config.moco.json in the current directory unless a path is specified.
-
help
Prints the available commands once moco is running. Output:
help Print the list of moco commands available list List all endpoints info <id>, <id> : integer Print the endpoint <id> information in readable way toggle <id>, <id> : integer Enable/Disable endpoint <id> response <id> <HTTPStatusCode>, <id> : integer, <HTTPStatusCode> : {100 - 999} Set endpoint <id> to answer with status code <HTTPStatusCode>
-
list
List all the endpoits loaded through the configuration file and its current state, i.e. which response is returning and if is active. Also the masked host will be printed if present
-
info <id>, <id> : integer
Prints in a human friendly way the information associated to the endpoint
<id>
. These ids are autogenerated by moco and can be found with the list command. -
toggle <id>, <id> : integer
Enable/Disable the endpoint
<id>
. -
response <id> <HTTPStatusCode>, <id> : integer, <HTTPStatusCode> : {100 - 999}
Set the endpoint
<id>
to answer with the status code<HTTPStatusCode>
. As mentioned before, if there are several responses with the same status code, the first one will be picked and if there is no response at all, moco will return an empty response.
Non-Profit Open Software License 3.0 (NPOSL-3.0)