JSONSVR enables you to quickly provide a mockup server that serves RESTful protocols. You write the API in service.json
and then provide corresponding responses for each API there. The responses can be read from .json
files, .html
files, .txt
, or even provided as a string. You can write Javascript expressions in the .json
or .html
files as well to create a template that could provide dynamic responses.
- Running the JSON server
- Configure the server using config.json
- Writing service.json
- API Definition
- JavaScript Expressions
- JSON template
- HTML template
- Support of S3 Hosted Files
- The Test Library API Reference
npm install -g jsonsvr
jsonsvr --init
jsonsvr
To configure the JSON server, write or modify the config.json
file. You can pull the
bootstraping config.json
file using the jsonsvr --init
command.
You may specify the path for your configuration file using the --config
parameter.
jsonsvr --config /path/to/config.json
The config.json
file example:
{
"port": "port number for the server to serve",
"imports": {
"module identifier": "module name"
},
"serviceDescriptor": "./data/service.json",
"noDefaultIndexPage": false,
"accessLog": "./access.log"
}
The port number to which the server is served on.
An optional imports
configuration allows you to load the node modules specified by module name
. Once successfully imported at startup, you may refer to this module inside the .json
or .html
files using the module identifier
. For example:
in config.json
:
{
...
"modules": {
"moment": "moment",
"uu": "uuid",
"lo": "lodash"
}
}
in orders.json
:
{
"trackingId": "$ctx.trackingId = uu.v4()",
"created": "$moment()",
"expire": "$moment().add(30, 'days')",
"$exec": "$ctx.orderIndex.push(ctx.trackingId); ctx.orderIndex = lo.uniq(ctx.orderIndex)"
}
Note: You'll need to issue
npm install <module>
to install external modules before being able to import them to the service.
You may specify the location of the service descriptor file using the serviceDescriptor
configuration parameter in config.json
file. The content of this parameter can be
- An absolute path on local machine
- A relative path against the current working directory
- A URI describing S3 object (see S3 Support for more detail)
The bootstraping template for service.json
can be generated using the jsonsvr --init
command.
By default, the server will be started with the root path /
being a documentation page (this document) if the root path was not defined in service.json
file. You can disable this behavior by setting the noDefaultIndexPage
to true
, in which the server will return 404-Not Found.
Set the accessLog
to the path of a log file or a special keyword console
to enable the full dump of requests and responses made by JSON server. In latter case, the access log is printed on the console.
You create service.json
file to define APIs. The service.json
file is loaded when the server starts. It is located at ./data/services.json
by default. However, you can specify the different location in serviceDescriptor
property on the config.json
file. You may need to restart the server if you made changes to the service descriptor file.
To define an API, use the route path as a key. Under the route path key, defines the HTTP methods, i.e. GET
, POST
, PUT
, DELETE
, OPTIONS
, PATCH
, HEAD
etc. A special method *
can also be specified if you want the route path to serve for every HTTP methods.
{
"/api/orders": {
"GET": {
"response": "./data/orders.json"
},
"POST": {
"response": "./data/orders_result.json"
}
},
"/api/users": {
"GET": {
"response": "./data/users.json"
}
}
}
The route path may contain parameters. JSON server uses the path parameter syntax used in express.js, which uses a colon-leaded parameter names within the route path. The parameter can later be accessed in the scripts using ctx.request.params.<parameter>
{
"/api/users/:userId": {
"GET": {
"response": "./data/user.json"
}
}
}
in user.json
:
{
"userId": "${ctx.request.params.userId}"
}
You may use the variables in service definition file, such as in path.
{
"/api/users/:userId": {
"GET": {
"response": "./data/${ctx.request.params.userId}/user.json"
}
}
}
Several ways to define a response for an API described in service.json
file. You must define one of the following response types for an API in order to get it to function.
response
- read from a fileerrorResponse
- respond with HTTP status coderedirect
- respond with 301 status code with redirect URLtextResponse
- response with the given textproxy
- forward the request to the specified URL and respond with the response from such URL
Note: The response types are mutual exclusive. They cannot be used together in a single API method.
Respond with the content of the specified file. The currently supported files are: .json
, .html
, and .txt
.
{
"GET": {
"response": "./data/response.json"
}
}
Respond with the given HTTP status code.
{
"GET": {
"errorResponse": "500"
}
}
Direct the browser to the redirected URL for this API. The redirect
parameter defines the target URL to redirect to. No query string are
being carried over from the original URL unless the redirectQueryString
parameter is set to true
. See redirectQueryString option
for more information.
{
"GET": {
"redirect": "http://www.google.com"
}
}
Carry over the query string from the request to the redirected URI. Note that all the query strings from the redirect
parameter will be replaced.
{
"GET": {
"redirect": "http://www.google.com/",
"redirectQueryString": true
}
}
Respond with the given text
{
"GET": {
"textResponse": "Hello"
}
}
You may use javascript expression in textResponse. See javascript expression for more details.
Forward the request to the specified URL and respond with the response from such URL
{
"GET": {
"proxy": "http://www.google.com"
}
}
Only use this particular method definition if the condition is met, i.e. the expression returns true
or a javascript truthly value. Otherwise, return 404 or the other method definition that has its condition matched. In the latter case, an array of method definitions has to be given instead of an method definition content.
{
"GET": {
"condition": "ctx.request.params.userId === '2931'",
"response": "./data/user2931.json"
}
}
From the example above, if the user ID does not equal 2931, a 404 error returns.
{
"GET": [
{
"condition": "ctx.request.params.userType === 'individual'",
"response": "./data/individual.json"
},
{
"condition": "ctx.request.params.userType === 'juristic'",
"response": "./data/juristic.json"
},
{
"response": "./data/default.json"
}
]
}
From the example above, the condition
is provided such that the given method definition would handle the matched case. The last definition, however, does not provide a condition. In such case the server assumes the true
condition. The evaluation starts from the first element of the method defition array. The first method definition that either not have condition or having the condition returns true, will be used. Others will be neclected for the particular request.
Note that giving an array of more than one method definitions does not make sense when all method definitions do not define
condition
. Doing so, only the first defition will always be excuted.
Define response headers
{
"GET": {
"headers": {
"Content-type": "text/html",
"Set-cookie": "session=29222"
}
}
}
Provide Javascript to run before response is sent. The script is provided as a string
.
Note: You do not need to use expression directive
${...}
in thepreScript
value.
{
"/api/query": {
"*": {
"preScript": "ctx.data.queries = []",
"response": "./data/query-result.json"
}
}
}
PreScript supports mutilple statements using array.
Provide Javascript to run after the response is sent. The script is provided as a string
.
Note: You do not need to use expression directive
${...}
in thepostScript
value.
{
"/api/product": {
"*": {
"response": "./data/product.json",
"postScript": "ctx.data.summary = Math.round(ctx.data.price)"
}
}
}
PostScript supports mutilple statements using array.
There are cases when initialization has to take place in order to prepare environments -- e.g. data structures -- for further usages. Such situations can be written as an $init
script in the service descriptor file.
For example, to initialize the users
array:
{
"$init": "ctx.data.users = []"
}
The $init
allows multiple statements. They can be written as an array:
{
"$init": [
"ctx.data.users = []",
"ctx.data.sessionIndex = {}",
"ctx.data.count = 0"
]
}
The initialization script runs only when the server starts, i.e. when the service.json
is loaded.
To allow dynamic generation of content or configuration, you may use javascript expression in your json values. The expression can be defined inside ${...}
directive and can be used in mixed with the string value.
The following is an example of how expressions can be used:
{
"values": "${a}, ${b}, ${c}",
"average": "${(a + b + c)/3}"
}
Note: If the expression spans an entire string value, the type of result will be that of the result of the expression. For example, if your expression returns a number, the property value will be a number. If you need the expression that returns numeric (or other primitive types such as boolean, null, or undefined) to be represented as a string, use the
String(...)
function. For example,"values": "${String(true)}"
would be interpolated to"values": "true"
, while the"values":"${true}"
results in"value": true
.
The JSON files you defined in the service.json
file as the values to the response
property are JSON templates. A JSON template can be a static content, having no expressions or directives, or dynamic, with expressions and/or directives within them. The dynamic generation of JSON file according to the template is a powerful feature of JSON server, which enables you to generate test cases that work for you.
The context variable can be accessed in the expression as a ctx
variable. There are useful properties of this context variable that you can use. They are:
ctx.req
represents the current request. It is a Request instance fromexpressjs
framework. Interesting properties are:ctx.req.body
provides the parsed body value. If the Content-type isapplication/json
then the body is a Javascript object represents that JSON. If the Content-type isform-data
, the body is key-value pair of param and value.ctx.req.headers
provides the HTTP request headers in key-value pairs.ctx.req.params
provides the named URI component in the route. For example,/api/users/:username
route will havectx.req.params.username
be that third element of the route.ctx.req.query
provides the key-value pairs of the parsed query strings from the URI.
ctx.data
represents the globally accessible data where you can declare variables to use with the other APIs.
In JSON templates, you can use JSON Server directive to control the fields. The JSON directive is described as a JSON object literal with the key (property name) beginning with a dollar sign ($
) denoting the command.
Used to create an array of repeating element based on the given template.
{
"data": {
"$array": {
"count": 10,
"element": "${ctx.i}"
}
}
}
The element template can include dynamic expressions as same as that applies with the JSON templates. A special variable ctx.i
is available in the context for the element template, which contains the current iteration number.
count
The number of generated elementselement
The element template.
Used to execute a Javascript statements without generating values in any outputs to the target field.
For example, for this json template:
{
"field": { "$exec": "ctx.lastCall = new Date()" }
}
Generates the output as:
{}
For multiple statements, use array.
{
"field": { "$exec": [
"ctx.data.today = moment()",
"ctx.data.tomorrow = ctx.data.today.add(1, 'days')"
]}
}
Reads records in a CSV file to create an array of templated elements.
{
"$csv": {
"file": "file path",
"element": "string, object, or any types",
"firstLineHeader": false,
"headers": "optional string array of header variable names, defined in the same sequence againt columns",
"delimiter": "optional delimiter. Default to comma"
}
}
parameters:
file
Specifies path to the CSV file.element
Defines the element template. Can be a string (with expressions), object, or any types.firstLineHeader
Set to true if the first line on the CSV file defines header. If so, the headers will be used as a subscript tocol
in the context variable. Default tofalse
.headers
Array of header names to override CSV file header, or when they are absent. The headers will be used as a subscript tocol
in the context variable.delimiter
A field delimiter for this CSV file.
Conditionally return the element defined in then
property if the condition
implies JavaScript truthly value. Otherwise, the else
element is returned, if defined. Otherwise, the target field is eliminated.
Note: You need to use Javascript expression
${...}
forcondition
if you plan to provide expression to evaluate rather than a constant string or a value.
{
"pricing": {
"$if": {
"condition": "${data.price > 200}",
"then": "expensive",
"else": "inexpensive"
}
},
"errorMessage": {
"$if": {
"condition": "${data.error}",
"then": "${data.errmsg}"
}
}
}
Note: Unlike the full-fleged languages, JSON-server DSL does not defer the evaluation of
then
statements to when the condition is true, as well as for anelse
when the condition evalues to false. JSON server pre-evaluates the entire JSON structure before directive evaluation so all expressions are evaluated regardless of thecondition
resulting value.
The current version does not support dynamic HTML content generation.
JSON Server supports AWS S3 hosted files in most of the configuration and descriptors that require file path. To refer to S3 stored content, simply use s3://
URI scheme in place of the path.
For example, in config.json
you may use:
{
"serviceDescriptor": "s3://your-s3-bucket-name/path/to/object.json"
}
Utilities that are commonly required for convenience in creating mock API are preloaded by default. This bundle of utilities can be accessed under lib
namespace. They can be used in any expression evaluations on both service definition file and the template files.
Use as an expression in place of if
statement. Condition returns the then
evaluation if the condition
is true. Otherwise, it returns the result of else
evaluation.
Note: The
if
and the conditional expression in Javascript can also be used. This interface is just provided for convenience.
lib.condition(condition: boolean, then: () => any, else: () => any): any
{
"days": "${lib.condition(ctx.days > 30, () => days - 10, () => days + 20)}"
}
Returns a random number or letters in the given character class as string for the given number of digits. Useful for generating random fix-length string IDs.
lib.randomDigits(len: number = 8, charClass: string = lib.CC_NUMBERS): string
len
optional number of digits to be returnedcharClass
optional string of available characters to pick at random- The library has provided constant strings for mostly used character classes
lib.CC_ALPHANUM
for Alphanumeric (numbers and capital letters, 0-9 A-Z)lib.CC_MIX_ALPHANUM
for Alphanumeric with mixed character cases (0-9 A-Z a-z)lib.CC_NUMBERS
for numeric digits (0-9)lib.CC_CAPITALS
for capital letters (A-Z)lib.CC_LOWERCASES
for lowercase letters (a-z)- You can also use them in mix, for example
lib.CC_LOWERCASES + lib.CC_CAPITALS
.
- The library has provided constant strings for mostly used character classes
{
"username": "${lib.randomDigits(lib.randomNumber(15,3), lib.CC_CAPITALS)}",
"password": "${lib.randomDigits(16, lib.CC_MIX_ALPHANUM + '!@#$%^&*()_-+=\\/.\\'\"<>?{}[]')"
}
Returns the random number in the range of the given [max, min] inclusive.
lib.randomNumber(max: number = 100, min: number = 0)
max
optional maximum possible number to return, inclusive. Default to 100min
optional minimum possible number to return, inclusive. Default to 0
{
"price": "USD ${lib.randomNumber(100)}",
"height": "${lib.randomNumber(210, 140)} cm"
}