npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

backend-middleware

v1.0.1

Published

The nodejs server middleware that serves json documents as http resources for user-defined or restful urls. Helps with developing web application html/js clients that will communicate with a backend serving the data user interface needs by mocking the bac

Downloads

16

Readme

About

Backend-middleware is a lightweight middleware for expressive HTTP middleware frameworks such as express.js meant to be used to mock a RESTful JSON web service so that a front-end can be developed rapidly and independently of the server.

Example use case

Suppose you need to consume a JSON api along the lines of:

https://mycompany.com/app-context/resource-name/:id
https://mycompany.com/app-context/resource-name?attribute=value&other-attribute=otherValue
https://mycompany.com/app-context/other-resource-name

which return JSON responses along the lines of:

{
    "id": 1,
    "attribute": "value",
    "otherAttribute": "otherValue",
    "embeddedObject": {
        "anotherAttribute": "differentValue"
    }
}

You can use backend-middleware to quickly bring up such a service by dumping JSON files into a single directory:

backend-middleware-config/data/resource-name.json:
[
    {
        "id": 1,
        "attribute": "value",
        "otherAttribute": "otherValue",
        "embeddedObject": {
            "anotherAttribute": "differentValue"
        }
    },
    ....
]

backend-middleware-config/mapping/resource-name.map.json
{
    "id": {
        "key": true
    }
}

And telling your express server to use backend-middleware:

const backendMiddleware = require('backend-middleware');

// ...

const backendMiddlewareConfig = {
    dataFiles: {
        path: './backend-middleware-config/data',
        extension: '.json'
    },
    resourceUrlParamMapFiles: {
        path: './backend-middleware-config/mapping',
        extension: '.map.json'
    }
};

// ...

app.use(backendMiddleware.create(backendMiddlewareConfig));

// ...

Backend-middleware implements common conventions in RESTful JSON APIs, so querying a collection resource will return you a list of all resources in that collection, filtering a collection can be done with query parameters matching the name of the attributes of the resource, and you can get individual resources by id.

Quick Start

Installation

npm install backend-middleware

Usage

You need to use body-parser middleware for backend-middleware to work with json objects in http request body.

var bodyParser = require('body-parser');
var backendMiddleware = require('backend-middleware');
var config = {
    dataFiles: {
        path: './example/middleware-config/data',
        extension: '.json'
    },
    resourceUrlParamMapFiles: {
        path: './example/middleware-config/mapping',
        extension: '.map.json'
    }
};
//to insert the pre-req middleware functions e.g.: when using express
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended:true}));
app.use(backendMiddleware.create(config));
//When using browsersync, see the example application bundled with the project. (see the server.js under example/server folder)

The two configuration parameters you have to specify at a minimum are your data file and resource to url parameter mapping.

Configuration

const config = {
	dataFiles: {
		path, 
		extension: '.json'
	},
	resourceUrlParamMapFiles: {
		path, 
		extension: '.url.param.map.json'
	},
	urlParameterDateFormat: 'YYYY-MM-DDThh.mm.ss.sss',
	computedProperties,
	routes,
	handlers,
	responseTransformerCallback,
	contextPath: 'backend-middleware',
};

Option | Explanation ------- | ------------ dataFiles.path | The path to the directory containing the JSON files to read. dataFiles.extension | The extension of the files. resourceUrlParamMapFiles | The JSON files that describe how URL parameters are mapped to JSON resource attributes, along with other things, such as defaults, and how to deserialize the parameter value. resourceUrlParamMapFiles.path | The path to a directory of url parameter mapping files resourceUrlParamMapFiles.extension | The extension of files to read in that directory urlParameterDateFormat | The date format to read date strings in the data files, if the parameter mapping file says the attribute has a type of 'date'. This uses the moment syntax. computedProperties | A Javascript object describing computed properties for all resources. routes | A Javascript object describing the routes. There are certain default routes. handlers | A Javascript object describing the handlers for routes. There are default handlers for default routes. responseTransformerCallback | A Javascript function which transforms the response from the middleware. contextPath | The url path prefix that the middleware will handle.

dataFiles and urlParameterDateFormat

Data files contain the JSON of the resources the backend-middleware serves. The file names have the form:

${dataFiles.path}${resourceName}${dataFiles.extension}

So, the file name determines the resource name, which is used through the rest of the middleware configuration.

When the data files are read, if the mapping file (see below) designates that an attribute has type date, then that attribute will be parsed as a date using the format specified in the config parameter urlParameterDateFormat using the moment.js library, and then stored as a unix millisecond timestamp (and hence will be served as a number).

backend-middleware-config/data/people.json:
[
    {
        "id":99,
        "firstName":"John",
        "lastName":"Doe",
        "birthDate": "1970-12-31",
        "boss":{
            "id":1001,
            "firstName":"Jackie",
            "lastName":"Chan",
            "team":{
                "id":100,
                "name":"Awesome"
            }
        }
    },
    {
        "id":100,
        "firstName":"Jane",
        "lastName":"Doe",
        "birthDate": "1965-11-21",
        "boss":{
            "id":1001,
            "firstName":"Jackie",
            "lastName":"Chan",
            "team":{
                "id":100,
                "name":"Awesome"
            }
        }
    }
]

resourceUrlParamMapFiles

resourceUrlParamMapFiles specifies location of the url parameter map files. The file names read have the format:

${resourceUrlParamMapFiles.path}${resourceName}${resourceUrlParamMapFiles.extension}

Hence, their file names match up with the resource names specified by the data files.

These files specify information about url parameters, and how they map to the resource attributes, along with implicitly specifying the type of the resource attributes. They have the following form:

/resource-name.url.param.map.json
{
    "some-url-param": {
        "attribute": "someResourceAttribute",
        "type": "numeric",
        "key": false,
        "defaultValue": "a value",
        "defaultOrder": "asc"
    },
    "another-url-param": {
        "attribute": "some.attribute.in.objects.embedded.in.the.resource"
    }
}

All properties are optional.

defaultOrder is only used by the sorting response transformer.

defaultValue is only used by the HandlerPayload.getParamOrDefault; see below.

type can be one of:

  • boolean
  • numeric
  • date
  • string (default)

attribute defines the attribute path on the resource that the url parameter is associated with. The default getter handler uses this to constrain which resources to return.

key defines the attribute to be a part of the database-like primary key of the resource. The default getter handler notices if the query uses the primary key url parameters to fetch a resource, and if so, returns a single object, instead of a list of matching objects.

backend-middleware-config/mapping/people.url.param.json:
{
    "employee-id":{
        "attribute":"id",
        "type":"numeric",
        "key":true
    },
    "last-name":{
        "attribute":"lastName"
    },
   
    "boss-id":{
        "attribute":"boss.id",
        "type":"numeric"
    }
}

computedProperties

The computedProperties is a Javascript object mapping resource names to objects of computed properties. Computed properties are given the resource, and must return the value of the computed property named by the name of the function. The this psuedo-variable of the function is set to the object holding the computed properties, so that computed properties can call each other. For example:

const computedProperties = {
	employee: {
		fullName(employee) {
			return `${employee.firstName} ${employee.lastName}`
		},
		isCurrent(employee) {
			return Date.now() < employee.appointment.endDate; 
		},
		isOnPayroll(employee) {
			return employee.appointment.isPaid && this.isCurrent(employee);
		}
	},
	otherResource: {
		otherProp() { /* ... */ }
	}
}

The values of computed properties are assigned onto the resource itself, and they are available when querying the database, and can be referenced in the resourceUrlParamMap files as an "attribute."

Feel free to import the resourceDao from backend-middleware/src/database/daos/resource.dao to build dynamic relationships between resources which update when entities change via PUTs or POSTs.

routes and handlers

Routes are specified by a Javascript object, mapping the route name to a string describing the url to match, in the uniloc.js format. For instance:

var routes = {
    'getEmployees': 'GET /:parentResourceName/:departmentId/:childResourceName',
    'postEmployees': 'POST /:parentResourceName/:departmentId/:childResourceName'
};
module.exports = routes;

Handlers are specified in the same way, but the route name is instead mapped to a handler function:

var routes = {
    'getEmployees': function(handlerPayload, responseTransformerCallback) {
    },
    'postEmployees': function(handlerPayload, responseTransformerCallback) {
    }    	
};
module.exports = routes;

The handlers must return a response object that looks like:

const handlerReturnValue = { 
  statusCode: 400,
  headers: {
  	headerName: 'headerValue'
  	// ...
  },
  body: 'body string'
};

The handlerPayload looks like:

const handlerPayload = {
	request: ExpressRequest,
	urlParameters: {parentResourceName, departmentId, childResourceName},
	parameterMapper,
};

The handler payload has a number of helper methods available. See the section below on it.

The urlParameters are those bound from the url in the handlers file, along with all query parameters. For example, if the url pattern for a handler was GET /resources/:id, and the url that matched it was /resources/18?page=2, then urlParameters would be:

const urlParameters = {
    id: '18',
    page: '2'
}

The RouteRegistrar

To simplify and unify the handlers and routes objects, there is a RouteRegistrar utility in backend-middleware/src/utils/route.registrar. It works like so:

const RouteRegistrar = require('backend-middleware/src/utils/route.registrar');
const routeRegistrar = new RouteRegistrar([
	{
		url: 'GET /some/resource/:with/:parameters',
		handler: function(handlerPayload, responseTransformerCallback) {
			// just as before
		}
	},
	{
		url: 'GET /some-url-for-things',
		resource: 'things',// use things.json
		handler: require('backend-middleware/src/handlers/resource.getter')
		// default handlers operate on the data file named by the "resource" key
	},
	{
		url: '/aResourceGroup',
		subroutes: [
			{
				url: 'GET',
				handler: function() {
					// a handler for GET /aResourceGroup
				}
			},
			{
				url: 'GET /:id',
				handler: function() {
					// a handler for GET /aResourceGroup/:id
				}
			}
		]
	},
]);
const routes = routeRegistrar.routes;
const handlers = routeRegistrar.handlers;

This allows you to automate common patterns in your configuration. For example:

const resourceGetter = require('backend-middleware/src/handlers/resource.getter');
const resourcePutter = require('backend-middleware/src/handlers/resource.putter');
const resourcePoster = require('backend-middleware/src/handlers/resource.poster');
function makeCollectionResource(name, resource = name) {
	return {
		url: `/${name}`,
		resource: resource,
		subroutes: [
			{
				url: 'GET',
				handler: resourceGetter
			},
			{
				url: 'GET /:$resourceId',
				handler: resourceGetter
			},
			{
				url: 'PUT /:$resourceId',
				handler: resourcePutter
			},
			{
				url: 'POST',
				handler: resourcePoster
			}
		]
	}
}

const routeRegistrar = new RouteRegistrar([
	makeCollectionResource('people'),
	makeCollectionResource('groups'),
	makeCollectionResource('groups/:groupId/members', 'people'),
	makeCollectionResource('employees'),
]);

Note that using $resourceId as a parameter with the default handlers makes a query on the attributes marked with "key": true in the parameter mapping file, and consequently causes them to return objects rather than arrays of objects, since the handler knows the query is unique.

HandlerPayload

The handler payload has a number of helper methods, but they require the handler payload to know about the resource name this request is meant to access. There is nothing that requires a single request to only access a single resource, however since that is typical, the handler payload gives you some shortcuts if that is the case.

You can specify the resource name to the handler payload by setting on the object itself:

handlerPayload.resourceName = 'myResource';

However, it will infer it in three cases:

  1. If there are at least two words in the route name, the resource name is taken as the first word.
  2. If you are using the route registrar, it is the value of the "resource" key.
  3. Otherwise, it is the value of the url parameter named $resourceName.

An example of the first case:

const routes = {
    'employees GET': 'GET /employees',
    'employees PATCH appointment': 'PATCH /employees/:id/appointment',
    'employees PUT': 'PUT /employees/:id'
};

and an example of the second case:

const routes = {
    'getPublicResource': 'GET /public/:$resourceName',
    'getProtectedResource': 'GET /secure/:$resourceName',
};

HandlerPayload helper methods

Once the resource name is set or inferred, the following handler methods will work:

MethodName | Description --- | --- getParamOrDefault(urlParameterName) | Return the value of a parameter, defaulting it based on the mapping file, and parsing it based on its type if the mapping file. getAttribute(urlParameterName) | Return the attribute of the resource that corresponds to the parameter, or itself if there is no explicit mapping. getParameterInfo(urlParameterName) | Return the JSON object for the parameter name, direct from the mapping file. parseValue(attributeName, value) | Parse the value for the attribute (not url parameter), according to its type. setValue(attributeName, resource, value) | Set the value of the attribute on the resource, parsing the value based on the type of the attribute.

contextPath

Specifies the context path after your server domain.

Default Handlers

There are default handlers for getting and posting a resource. The getter takes a resource like:

GET https://localhost:3000/${contextPath}/${resourceName}?first-name=Phil

And return a response like:

[{
    firstName: "Phil",
    lastName: "Smith",
    ...
}, {
    firstName: "Phil",
    lastName: "Johnson",
    ...
}]

It uses the url parameter mapping to find a matching attribute for each url parameter provided, and filters the JSON objects in the data file to only those that match the provided values (after parsing the url parameter values based on the parameter type).

Similarly, the following works:

GET https://localhost:3000/${contextPath}/${resourceName}/87

which would return a JSON object of the resource with id equal to 87.

Finally, posting to the resource with a JSON object would add that object to the (in-memory) database. The data files themselves are never changed.

Computed properties can be queried in this way, and they are returned in the response of any request.

responseTransformerCallback

This config parameter specifies a callback to process the response before returning to the client side. This will allow you to transform the response any way you want. See the example app for a server side pagination example. The function signature for a response transformer is as follows:

const responseTransformerCallback = function (handlerPayload, handlerResponse) {
	return handlerResponse;
}

Example app

How to run

npm start

URLs

  • get all employees: https://localhost:3000/employees
  • get one employee by id: https://localhost:3000/employees/1 (you can also try id 2, 3 and 4)
  • get employees by query string: https://localhost:3000/employees?last-name='Doe'&boss-id=10 (check the data file: middleware-config/data/employees.json for more details)
  • get employees by query string (server side pagination): https://localhost:3000/employees?last-name='Doe'&boss-id=10&page=1&page-size=5

     If the same query string parameter is specified multiple times, by default numeric and date parameters uses "between" operator if parameter is repeated twice or uses "in" operator.

  • get employees whose birth date is between 1970-12-31 and 1965-01-01: https://localhost:3000/employees?dob=1970-12-31&dob=1965-01-01
  • get employees whose birth date is one of 1970-12-31, 1967-11-01, 1978-01-01: https://localhost:3000/employees?dob=1970-12-31&dob=1965-01-01&dob=1978-01-01
  • login (need to use something like Postman to send a POST request, valid usernames and passwords are in example/middleware-config/users.json): https://localhost:3000/login

Files

  • main file: example/server/server.js
  • data files: example/middleware-config/data
  • url parameter to resource attribute mapping files: example/middleware-config/mapping
  • customized routes: example/routes.js
  • customized handlers: example/handlers.js
  • customized response transformer: example/response.transformer.js
  • valid usernames and passwords for testing login: example/middleware-config/users.json