Skip to content

Latest commit

 

History

History
 
 

data-point-express

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 

DataPoint Express

Build Status codecov Coverage Status All Contributors

Create DataPoint service with Express middleware support

Requirements

Install

NOTE: Express and DataPoint are peer dependencies

npm install --save express data-point data-point-express 

Quick start

A simplistic example of creating a DataPoint Express service.

const express = require('express')
const Service = require('data-point-express')

const app = new express()

Service.create({
  // add DataPoint entities
  entities: {
    'entry:hello-world': () => 'Hello World!!'
  }
})
.then((service) => {
  // expose DataPoint inspector
  app.use(
    '/api/inspect', 
    service.inspector()
  )
  
  // maps route: /api/hello-world to
  // entityId: entry:hello-world
  app.get(
    '/api/hello-world', 
    service.mapTo('entry:hello-world')
  )

  // start Express server
  app.listen(3000, (err) => {
    console.log('DataPoint service ready!')
  })
})

The code above should expose two paths:

Create a DataPoint Service Object

This method returns a Promise that resolves to a DataPoint Service Object.

Service.create({
  entities: Object,
  entityTypes: Object,
  cache: {
    localTTL: Number,
    redis: Object,
    isRequired: true,
    prefix: String
  }
}):Promise<Object>

The following table describes the properties of the options argument:

option type required description
entities Object yes DataPoint entities Object.
entityTypes Object optional Create your DataPoint custom entity types.
cache.localTTL Number optional Value in Milliseconds of in memory TTL, by default it's set to 2000 (2 seconds)
cache.redis Object optional Value passed to the ioredis constructor
cache.isRequired Boolean optional Defaults to false. If true the service will throw an error when getting created.
cache.prefix String optional Defaults to os.hostname(). In production you may be using multiple node instances and might want to instead share the prefix.

Example

Creates a new DataPoint Service:

const express = require('express')
const Service = require('data-point-express')

const app = new express()

Service.create({
  // add DataPoint entities
  entities: {
    'entry:HelloWorld': (input, acc) => 'Hello World!!',
    'entry:Greet': (input, acc) => `Hello ${acc.locals.params.person}!!`
  }
})
.then(service => {
  // create Express routes
  app.get('/api/hello-world', service.mapTo('entry:HelloWorld'))
  app.get('/api/greet/:person', service.mapTo('entry:Greet'))

  app.listen(3000, (err) => {
    if(err) {
      throw err
    }
    console.info('DataPoint service ready!')
  })
})
.catch(error => {
  console.info('Failed to Create Service')
  console.error(error)
  process.exit(1)
})

Service: http://localhost:3000/api/hello-world

Returns:

Hello World!!

Service: http://localhost:3000/api/greet/darek

Returns:

Hello darek!!

When Service.create is resolved it returns a service instance that exposes the following api:

Maps a DataPoint entityId to a middleware method. This method returns an Express Middleware function.

service.mapTo(entityId:String):Function

arguments:

argument type description
entityId String DataPoint entity Id.

Example:

Maps path '/api/hello-world' to entityId 'entry:HelloWorld'

app.get('/api/hello-world', service.mapTo('entry:HelloWorld'))

Create DataPoint aware routes. This method returns a Express.Router Object.

service.router(routes:Object):Router

arguments:

argument type description
routes Object Routes Object

Example:

Create a set of routes under the path '/api'. Notice how you can set the http method on each route as well as the priority.

app.use('/api', service.router({
  helloWorld: {
    priority: 100,
    path: '/hello-world',
    method: 'GET',
    middleware: 'reducer:HelloWorld'
  },
  addUser: {
    priority: 200,
    path: '/user',
    method: 'POST',
    middleware: 'entry:addUser'
  },
  deleteUser: {
    priority: 300,
    path: '/user',
    method: 'DELETE',
    middleware: 'entry:deleteUser'
  }
}))

This object must follow a specific structure:

{
  routeId: {
    path: String,
    priority: Number,
    enabled: Boolean, 
    method: String,
    middleware: Array<Function|String>|Function|String
  }
}

Each property of a route is described in the table below:

property type description
path String Valid Express route
priority Number Number to order the routes, since in express this order matters make sure you place these numbers correctly
enabled Boolean Enable/disable a route from being added. true by default, unless explicitly set to false
method String http method mapped to the route. Defaults to 'GET'. Available methods: 'GET', 'PUT', 'DELETE', 'POST'.
middleware Array<Function|String> This is the actual middleware function used for the route. For information on how to use please look at route.middleware

Route Middleware can be written in multiple ways:

Using standard express functions, it accepts standard Express middleware methods as described in using express middleware.

Example:

{
  helloWorld: {
    path: '/hello/:person',
    priority: 100,
    middleware: (req, res) => res.send('hello ${req.params.person}!')
  }
}

You must pass a string that points to a valid DataPoint entity id; this maps the middleware to the given entity Id. The entity's resolution becomes the result sent to the client.

Example:

// data point entities:
{
  'entry:HelloWorld': {
    value: (input, acc) => `hello ${acc.locals.params.person}!`
  }
}

// routes
{
  helloWorld: {
    path: '/hello/:person',
    priority: 100,
    middleware: 'entry:HelloWorld'
  }
}

You may also use a mix of functions and entity ids, for example you may want to do authentication or parameter normalization before executing a DataPoint entity.

One caveat is that you may only pass one entity id and it must be the last middleware otherwise it throws an error.

Example:

// data point entities:
{
  'entry:HelloWorld': {
    value: (input, acc) => `hello ${acc.locals.params.person}!`
  }
}

// routes
{
  helloWorldProtected: {
    path: '/hello/:person',
    priority: 100, 
    middleware: [requireAuthentication, 'entry:HelloWorld']
  },
  badRoute: {
    path: '/hello/:person',
    priority: 100, 
    // this is not allowed, entity
    // must be at the end.
    middleware: ['entry:HelloWorld', requireAuthentication]
  }
}

This service comes with a DataPoint entity inspector web interface. To mount you must pass the result of service.inspector() to Express app.use. Make sure you specify a path where to mount the inspector.

IMPORTANT: for security do not expose this middleware in production environments.

Basic implementation example:

const express = require('express')
const Service = require('data-point-express')

const app = new express()

Service.create({
  // DataPoint entities
  entities: {
    'entry:hello-world': (input, acc) => 'Hello World!!'
  }
})
.then((service) => {
  // expose DataPoint inspector
  app.use('/api/inspect', service.inspector())

  app.listen(3000)
})
.catch(error => {
  console.info('Failed to Create Service')
  console.error(error)
  process.exit(1)
})

Working example at /examples/inspector-demo.js

Accumulator.locals

When an entity executes through a DataPoint Middleware, it appends some useful information to the Accumulator.locals property. These values are persistent across the execution of the request.

Property Type Description
routeRequestType String Type of route request being made: 'api', 'rdom', 'html'
req Express.RequestObject Reference to current Express req
url String Node's message.url
query Object Reference to current Express req.query
params Object Reference to current Express req.params
queryParams Object This is a defaults merge of req.query with req.params
paramsQuery Object This is a defaults merge of req.params with req.query

Example:

DataPoint Reducer that prints out the acc.locals.url to the console.

const reducer = (input, acc) => {
  console.log('url that originated this call:', acc.locals.url)
  return input
}

Please read CONTRIBUTING.md for details on our code of conduct, and the process for submitting pull requests to us.

This project is licensed under the Apache License Version 2.0 - see the LICENSE file for details