mockfoundry

1.0.0-alpha.2 • Public • Published

MOCKFOUNDRY

Gitlab pipeline status Coverage License Bundle Size NPM Version NPM Collabs

Motivation

  • Prototyping a UI and want a quick way of testing API/CRUD functionalities?
  • Working on a feature with external incomplete API dependencies?
  • Ever wanted a very simple RESTful server with built-in CRUD operations?

If you answered yes to any of the questions stated above then mockfoundry may be right for you.

Goal

  • To drastically reduce the boilerplate needed for prototyping with actual data.
  • To enable true mocking-out of APIs and still achieving close to actual world behavior.

Installation

npm i --save mockfoundry // as a dependency
npm i --save-dev mockfoundry // as a development dependency. can be used for testing

Use Require

This library is meant to be run in Node.js and therefore is exported as a CommonJS library

const Mockfoundry = require('mockfoundry'); // require module

Port

Port is an essential part of mockfoundry as it is needed to create instances for the server. It is also used in the naming convention for the files in which data is saved.

If you start one instance on port 1000 and you start another instance on port 1001, I hope you would agree data saved on disk from those endpoints should be saved in their respective files. For that reason, mockfoundry treats ports as part of the data file name and maintains them accordingly,thus, mockfoundry fundamentally treats ports as databases.

The data will persist on disk even between restarts for the same port unless the override flag is set during instantiation so please tag ports accordingly.

Instance

const instance = new Mockfoundry(port [, override ]);

Run

const instance = new Mockfoundry(1000); // create instance on port 1000
instance.start(); // start server

API Primer

All APIs have a similar syntax convention:

host:port/action/collection?[optional-query-parameters]
Type Meaning Required Default(s)
host host of server Yes localhost (and is only localhost for now)
port port instance is running on Yes No default
action crud operation to be performed by the server Yes save, fetch, count, update, remove (and these are the only ones that can be used)
collection the type of data being saved. It is synonymous to a table in SQL or a type of document in a NoSQL Yes No Default
optional-query-parameters additional query parameters that can be used along with the fetch action to optimize the data returned No skip, limit, sort (and these are the only ones that can be used)

HTTP Method Standards

Mockfoundry adheres to the HTTP standards with all API calls. Simply put, all actions use appropriate HTTP methods for respective calls.

Action HTTP Method Accepts Body Requires Body Required In Body Optional In Body
save POST Yes Yes array of objects or an object ---
fetch GET No --- --- ---
fetch POST Yes No --- fields and filters
count GET No --- --- ---
count POST Yes No --- filters
update PUT Yes Yes filters and update ---
remove DELETE Yes Yes filters ---

APIs

Let's take a Vehicle Collection. We will proceed to learn how to do all CRUD operations in mockfoundry using our Vehicle Collection. Our Vehicle Collection has the following structure:

{
    brand
    model
    year
    kind
    countryOfOrigin
    spec {
        engine
        drivetrain
        acceleration
        speed
        range
    }
    class
    isElectric
    isStreetLegal
    features
    safetyRating
    awards
    tags
}

The way in which we wrote out this collection will become particularly important later on. Other than that, it is a simple list of fields a Vehicle Collection can have.

All our examples will be explained using axios --- a promise based HTTP client for the browser and node.js.

Save API

At this point, mockfoundry does not enforce any kind of schema validation on a collection level. Anything you save in the collection will be what gets saved. Mockfoundry is first and foremost a mocking library and as such, we want to reduce the boilerplate setup needed to achieve just that.

For the sake of brevity and code readability, we will shorten what we show as being saved but in actuality, we are saving the whole structure described above.

Example --- Single Entry

axios.post(
    'locahost:1000/save/vehicle',{
        brand: 'bmw',
        model: 'm4',
        year: '2020',
        ...... // indicates more fields
    }
)
.then(response => console.log(response)) // prints true if saved, else false
.catch(error => console.error(error)); // prints kind of error

Example --- Multiple Entry of the same collection

axios.post(
    'locahost:1000/save/vehicle',[
        {
            brand: 'bmw',
            model: 'm4',
            year: '2020',
            ...... // indicates more fields
        },
        {
            brand: 'toyota',
            model: 'prius,
            year: '2021',
            ...... // indicates more fields
        }
    ]
)
.then(response => console.log(response)) // prints true if saved, else false
.catch(error => console.error(error)); // prints kind of error

Fetch API

Looking back at the API Primer and HTTP Method Standards, you will notice that the fetch action is a very special action. To reiterate, let's list all the combinations of things it can have:

  • It can be called with or without a body
  • It is the only action that accepts optional query parameters
  • A new fun fact that was not apparent from before is, it can accept any arbitrary number of fields in exactly the structure described at the beginning of the APIs section. This way of requesting fields is inspired by the get-what-you-want model in graphql.

Example --- Simple Fetch

axios.get('locahost:1000/fetch/vehicle')
.then(data => console.log(data)) // prints an array of vehicle collection objects
.catch(error => console.error(error)); // prints kind of error

Example --- Fetch with Limit as Query Parameter

axios.get('locahost:1000/fetch/vehicle?limit=2')
.then(data => console.log(data)) // prints an array of two vehicle collection objects
.catch(error => console.error(error)); // prints kind of error

Example --- Fetch with Skip and Limit as Query Parameters

axios.get('locahost:1000/fetch/vehicle?skip=1&limit=2')
.then(data => {
    // prints an array of two vehicle collection objects
    // by skipping the first object it finds in the collection
    console.log(data)
})
.catch(error => console.error(error)); // prints kind of error

Example --- Fetch with Skip, Limit, and Sort as Query Parameters

axios.get('locahost:1000/fetch/vehicle?skip=1&limit=2&sort=year')
.then(data => {
    // prints an array of two vehicle collection objects
    // by skipping the first object it finds in the collection
    // and sorting by year ascending order by default
    console.log(data)
})
.catch(error => console.error(error)); // prints kind of error

To specify a particular sort order, other than the default ascending, you need to set it with a colon character:

sort=year:desc
sort=spec.acceleration:asc

For multiple sorts, use the pipe character to list them out :

sort=year:desc|spec.acceleration:asc
sort=year|isElectric:desc

Example --- Fetch with Filter(s)

axios.post(
    'locahost:1000/fetch/vehicle',{
        "filters": [
            {
                "field": "kind",
                "op": "$eq", // special operator character. $eq means equals to.
                "value": "sedan"
            }
        ]
    }
)
.then(data => {
    // prints an array of vehicle collection objects
    // that matches the filter(s) passed in
    console.log(data)
})
.catch(error => console.error(error)); // prints kind of error

You can head over to the Filters section to learn how to use them. It is very straight forward.

Example --- Fetch with Field(s)

axios.post(
    'locahost:1000/fetch/vehicle',{
        "fields": `{
                brand
                model
                year
                kind
                spec {
                    range
                    speed
                    engine
                }
                class
                tags
        }
        `
    }
)
.then(data => {
    // prints an array of vehicle collection objects,
    // wih only the fields listed in the fetch call
    console.log(data)
})
.catch(error => console.error(error)); // prints kind of error
A little about Fields

As mentioned earlier, the fetch action has special powers among which is the ability to list the fields you want. Thanks to ES6 string literals, we can list fields we want to return in a multiline format. It has many benefits such as, code readability, familiarity due to its object-like look and getting data in exactly the way fields are listed. Of course you can resort to using a single line string if the ES6 approach is not viable. The only issue with that is, you loose code readability and I know that will drive me nuts.

To maintain complaince with this structure, mockfoundry applies the following validation rules to a fields property:

  • Needs to start with an opening curly brace ({).
  • Needs to end with a closing curly brace (}).
  • May contain spaces, tabs or new lines.
  • Contents between opening and closing curly braces can only be any of the rules stated above along with alphanumerics, commas, and underscores.

Count API

The count action is similar to the fetch action but different in one obvious way --- it does not need fields or query parameters.

Example --- Simple Count

axios.get('locahost:1000/count/vehicle')
.then(data => console.log(data)) // prints the number of documents if any, zero if none
.catch(error => console.error(error)); // prints kind of error

Example --- Count with Filter(s)

axios.post(
    'locahost:1000/count/vehicle',{
        "filters": [
            {
                "field": "kind",
                "op": "$eq", // special operator character. $eq means equals to.
                "value": "sedan"
            }
        ]
    }
)
.then(data => {
    // prints the number of documents
    // that matches the filter(s) passed in if anym zero f none
    console.log(data)
})
.catch(error => console.error(error)); // prints kind of error

Update API

The update action is similar to the count action. However, it requires another property called update. I know, very original.

Since mockfoundry does not enforce any schema validation due to reasons stated earlier, the update action merges new fields with the rest of the collection.

NOTE:

Updates of inner objects must be done with a dot notation (parent_prop.child_prop) if you want to keep other values that might be in there. Otherwise, if you have all the required data on the client side, you can use the spread operator or any other merge solution to add new data in before updating.

Use the fetch action to select the fields you want to return.

Example --- Update

axios.put(
    'locahost:1000/update/vehicle',{
        "filters": [
            {
                "field": "kind",
                "op": "$eq", // special operator character. $eq means equals to.
                "value": "sedan"
            }
        ],
        "update": {
            "year": 2021,
            "spec.engine": "v6" // note dot notation
            "spec.newProp: "some new value"  // note dot notation
            "anotherNewProp: {}
        }
    }
)
.then(response => console.log(response)) // prints true if updated, else false
.catch(error => console.error(error)); // prints kind of error

Remove API

The remove action is similar to the count action but different one obvious way --- it requires the filters property.

Example --- Remove with required Filter(s)

axios.delete(
    'locahost:1000/remove/vehicle',{
        "filters": [
            {
                "field": "kind",
                "op": "$eq", // special operator character. $eq means equals to.
                "value": "sedan"
            }
        ]
    }
)
.then(response => console.log(response)) // prints true if removed, else false
.catch(error => console.error(error)); // prints kind of error

Note that, mockfoundry treats empty filters array as match all. In that sense, an empty filters array is still an valid array and will cause all data of the passed collection to be removed. If that is your intention then ignore this caution.

Filters

All filter objects follow the field-op-value paradigm. That is because mockfoundry uses NeDB under the hood for database operations.

Besides the op property in a filter contstruction, the field and the value properties are self explanatory. The op property is either a logical or comparison operator. Below are the list of all supported operators:

Operator Meaning What question does it asks of the collection? Value Type(s)
$eq equal for the given field, is any value equals what was passed? string, number, regex, boolean, object, array
$lt less than for the given field, is any value less than what was passed? string, number
$gt greater than for the given field, is any value greater than what was passed? string, number
$lte less than or equal for the given field, is any value less than or equal to what was passed? string, number
$gte greater than or equal to for the given field, is any value greater than or equal to what was passed? string, number
$neq not equal for the given field, is any value not equal to what was passed? string, number, regex, boolean, object, array
$exists exists for the given field, does the field exist (when value is true) or not exist (when value is false)? boolean
$regex regex for the given field, does any value match the regex expression passed? string
$not not for the given field, is the value passed not an exact match of anything in the collection? string, number, boolean, object, array
$in in for the given field, is the value passed a member of the array of values in the collection? array of primitives or objects
$nin not in for the given field, is the value passed not a member of the array of values in the collection? array of primitives or objects
$or or for the given field, is any of the array of values passed exactly a value in the collection? string, number, boolean, object, array
$and and for the given field, are all of the array of values passed exactly a value in the collection? string, number, boolean, object, array

Besides the $eq and $neq operators, the rest are direct replicas of what NeDB uses officially.

Error

Error is an unexpected but important part of any application. Mockfoundry uses fastify as its application server and draws on its error handling structure and even extends it where possible.

All errors in mockfoundry contain the following properties:

Property Meaning Always Present
statusCode HTTP Code Yes
error Type of error Yes
message Reason for the error Yes
optionals A spread of any aditional properties specific to the action being performed No

Besides the optionals property, all other properties are default and directly borrowed from fastify. The optionals property is an object that merges its contents with the defaults so there is no actual optionals property key in errors.

Issues

Please report issues as you see them during usage. It will help improve this library as a whole. Thank you.

Credits

Package Sidebar

Install

npm i mockfoundry

Weekly Downloads

0

Version

1.0.0-alpha.2

License

MIT

Unpacked Size

45.4 kB

Total Files

32

Last publish

Collaborators

  • koficodedat