1.0.16 • Published 4 years ago

lemur-api v1.0.16

Weekly downloads
3
License
ISC
Repository
github
Last release
4 years ago

Disclaimer

This project is not affiliated with Postman in any way. This is a beta. Please use with care.

lemur-api

Lemur checks body structure, sanitizes inputs and documents endpoints in your postman collection. You can also use lemur to provide your front end with a JSON based documentation to match client and server side validation of inputs or generate forms automatically.

npm install lemur-api

Response structure

Lemur enforces a response structure similar to that of JSON-RPC 2.0. The main difference being that the error object is always defined. The result object will simply be ignored if error.code is not 200. Example response:

{
	"result": "Hello World",
	"error":
	{
		"code":200,
      		"msg":"success",
		"data":{}
	}
}

Postman

See the example below on how to connect your Postman collection with lemur.

To update your Postman collections:

lemur.updateAllCollections()

or from the command line (once your application is running):

pr-ud

Keep in mind that the use of the first option may lead you to reaching your Postman API limits quite fast, especially when using nodemon.

Body & Query

Lemur requires a body parser. Single parameters will then be parsed as outlined by the endpoint description. Parameters not included in the endpoint description will be ignored and will remain in req.body and req.query respectively. If a required parameter is missing or if a parameter does not fit the defined schema, the response will be rejected with a BAD_REQUEST error. Example:

router.add(
{
	//[...]
	//see the example below on how to use add()
	
	params:
	{
		someObject:
		{
			//any json schema
			type: 'object'
		}
	},
	
	//will return "object" if someObject is a valid JSON object
	callback: (req) => { return typeof(req.body.someObject) }
})

Files

Use the files property to make sure required files are present and that all files have the correct mime type. Make sure you use express-fileupload before adding any endpoint that makes use of the files property.

//use express-fileupload before defining this endpoint
router.add(
{
	//[...] 
	//see the example below on how to use add()
	
	//define files
	files:
	{
		thumbnails:
		{
			description: 'Posters for the gallery.',
			mimetypes: ["image/png", "image/jpeg"],
			required: true
		},
	},
	callback: (req) =>
	{
		//do something with the uploaded files in here
		return req.files.thumbnails
	}
	
})

Callback & callback chains

Every endpoint requires at least one callback. Callbacks are compatible with express callbacks but are wrapped in a try-catch block so that anything returned from the callback will be treated as the end of the callback chain and sent to the client. Any Exception thrown in a callback will also terminate the chain. To send an Exception to the client, use an APIError. Any other exception will be treated as an internal server error. Callbacks may be asynchronous.

Example callback chain:

[ requireLogin, secondCallback, () => { return 'You are logged in!' } ]

Example with an APIError:

function failedCallback(req, res, next)
{
	throw new lemur.APIError()
	.code(400)
	.msg('errormsg')
	.data({ foo: 'bar' })
} 
function failedCallback2(req, res, next)
{
	//use a pre-defined error code
	throw lemur.ERRORS.BAD_REQUEST()
	.msg('errormsg')
	.data({ foo: 'bar' })
} 

Internal Errors

By default anything thrown inside the callback chain that is not of type APIError will result in an internal server error being sent to the client. Nothing else will happen. If you need to log or diplay an errors, define an error handler:

lemur.onInternalError((req, res, error) => {})

Example

The following example might seem like a lot of code but keep in mind that the options will only have to be defined once.

Run this example:

npm run example
require('dotenv').config()

if(!process.env.POSTMAN_API_KEY)
{
	console.error('Missing postman API key in .env file.')
	console.error('.env file must be placed in module root.')
}

const lemur = require('../')

const baker = require('../lib/schema-baker')

const express = require('express')

const SERVER_PORT = 1337

/**
	Create a new options preset to re-use across different files and routers

	Do this once in your project before creating any routers.
*/
lemur.options('internal-api',
{
	//provide lemur with your express version
	express: express,

	//provide lemur with host info for documentation purposes
	host: 'localhost',
	port: SERVER_PORT, 
	protocol: 'http',

	mountpath: '/',

	//default request methods if none is supplied in an endpoint definition
	method: ['POST'],

	//link up your postman collection
	postman: 
	{
		apikey: process.env.POSTMAN_API_KEY,
		collection_uid: process.env.POSTMAN_COLLECTION_UID
	},

	//a list of schemas if you want to use $ref
	schemas: 
	{
		date:
		{
			//a json schema for a date which can be referenced using $ref
			id: '/date',
			type: 'string',

			//this is redundant and merely for the purpose of the example
			//since we use the process function to cast to Date anyway
			pattern: /^\d{1,2}\/\d{1,2}\/\d{4}$/,
		}
	}
})

//create a new router using our defined options
//if you only use one router, you can also put the options in there directly
const router = new lemur.LemurRouter({ use: 'internal-api' })

//use the add function to add a new endpoint
router.add(
{
	//this endpoint will answer POST and GET requests
	method: ['POST', 'GET'],

	//express route string
	route: '/echo-date',

	//for documentation
	description: 'Returns a date as a stringified date object.',

	//outline the expected query parameters
	query: 
	{
		date: 
		{ 
			//use $ref if you want to use a schema from the "schemas" option
			$ref: '/date',

			//the example will be the default value on postman
			example: '09/17/1997',

			//we use the process function to turn the string into a Date object
			//if this throws an exception, the string is rejected
			//this is not standard for json schemas
			//the process function is not required
			process: (str) => 
			{
				let timestamp = Date.parse(str)

				if(!isNaN(timestamp))
				{
					return new Date(timestamp)

				}else
				{
					throw lemur.ERRORS.BAD_REQUEST()
					.msg('invalid date')
				}
			},

			//by default this is false. the request will fail if no date is provided
			required: true
		}
	},

	//express callback or array for a callback chain
	//return something to trigger res.send()
	//throw an APIError to send it to the client
	//any other exception will result in an internal server error being sent to the client
	callback: (req) =>
	{
		//just echo the parsed date
		return req.query.date
	}
})

router.add(
{
	method: ['POST', 'GET'],

	route: '/typeof',

	description: 'Returns type of JSON object.',

	query:
	{
		someObject:
		{
			type: 'object'
		}
	},
	
	callback: (req) => { return typeof(req.query.someObject) }
})


/**
	Error handling example
*/
//this route will always produce an error
router.add(
{
	method: 'ALL',

	route: '/internal-error',

	callback: () =>
	{
		//produce an error that is not an APIError
		return thisVarIsNotDefined.attr
	}
})

//anything thrown in the callback that is not an APIError will cause this handler to be executed
lemur.onInternalError((req, res, error) =>
{
	console.error(error)
})


console.log(JSON.stringify(lemur.bakeParams('POST', '/echo-date')))

//create an express app like normal
const app = express()

//add your favourite body parser
app.use(express.urlencoded({extended: false}))

app.use(router.getRouter())

app.listen(SERVER_PORT, () => console.log('Server running on port ' + SERVER_PORT) )
1.0.16

4 years ago

1.0.15

4 years ago

1.0.14

4 years ago

1.0.13

4 years ago

1.0.12

4 years ago

1.0.11

4 years ago

1.0.10

4 years ago

1.0.9

4 years ago

1.0.8

4 years ago

1.0.7

4 years ago

1.0.6

4 years ago

1.0.5

4 years ago

1.0.3

4 years ago

1.0.2

4 years ago

1.0.1

4 years ago

1.0.0

4 years ago