@pubcore/node-composition
v2.14.3
Published
composition of components served by express
Downloads
66
Readme
Express middleware to map requests to functions of components
Within the terminology of HTTP we do have requests and responses. Given a response is just a function of a request, this package provide the option to configure such functional mappings.
In order to structure such functions the words "component" and "composition" are
used in this way:
A component is a set of functions.
A composition is a set of components.
A requested domain (on a specific port) is mapped to a composition.
The purpose of this package is to support such a structure by configuration.
Prerequisites
- nodejs
- expressjs
Auto invalidation of require-cache in development mode
Since nodejs caches required (imported) packages, changes within a file does
not affect a running express server, it needs to be restared. This is expensive
for continuously change-save-and-review cycles web-developers love.
This package implements automatic invalidation of require-cache per
component-package level. If a script file changes, all modules of corresponding
component-package gets invalidated.
Example composition
Let's assume we compose a todo-list component together with a calendar component. Composition's package directory consists of:
config.js
package.json
server.js
config.js (map request to components based on context-path)
//composition config
module.exports = {
//a composition is a set of components ...
components:{
'@yourOrg/todo-list':{
public: true,
context_path: '/todo'
},
'@yourOrg/calendar':{
public: true,
context_path: '/calendar'
}
}
}
npm's package.json
{
"name": "@yourScope/example-composition",
"version": "1.0.0",
"main": "server.js",
"dependencies": {
"express": "^4.17.1",
"@pubcore/node-composition": "^2.8.0",
"@yourScope/todo-list": "^0.1.0",
"@yourScope/calendar": "^0.1.0",
}
}
server.js
const
express = require('express'),
app = express(),
composition = require('@pubcore/node-composition').default,
config = require('./config.js')
app.use('/', composition(config, require))
Configuration options
module.exports = {
componentDefault:{
//if true, login (next option) is required
public: false
//login middleware, required, if component is not public
login: (req, res, next) => {next()},
//optional, build arbitrary data added to req.resources
resources: async (req) => {}
//optional error handler middleware
error: (err, req, res, next) => {},
//optional urlencoder middleware
//see http://expressjs.com/de/api.html#express.urlencoded
urlencoded: {extended: true}
},
components: {
"@company/component-one":{
//component ID
id: "@company/component-one"
//context path used for express Router
context_path: "/basePathOfComponentOne"
//optional to define (overwrite) defaults, see "componentDefault" ...
}
},
accesscontrol:{
//see https://developer.mozilla.org/en-US/docs/Glossary/CORS
//CORS headers are responded for requests send from sites of following
allowedOrigins: ["https://foo.net"],
//see https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
contentSecurityPolicy: "default-src 'self' data:; script-src 'self' font-src https:; style-src 'unsafe-inline' https:;"
//Cross Site Request Forgery (SCRF) Protection by Double Submit Cookie Pattern
//see https://github.com/expressjs/csurf
//Secure config to store token-secret by cookie:
csrfProtection: {key:'__Host-Csrf', secure:true, sameSite:'lax', httpOnly:true}
},
//optional
options:{
//optional
requestJsonLimit: '2mb' //default 100kb
}
}
Example component
A component package exports the mapping of URI sub-path to a express middleware function:
- src/index.js
//import express middleware functions
import list from './lib/getList'
import addItem from './lib/addItem'
export default {
public:true,
http: [
{
routePath: '/list',
map: list,
method: 'GET',
accepted: ['text/plain']
},
{
routePath: '/list',
map: addItem,
method: 'POST',
accepted: ['application/json'],
urlencoded: {extended: true} //optional, see above
},
]
}
- Optional, public "htdocs" directory contain some static files (e.g. imgage, css, js)
htdocs/
Features test output
compose components by configuration
✓ serves requests for some configured component functions
✓ serves requests for configured second "component-two"
✓ requires a login middleware function, if component is private
✓ reloads modules in development mode, if corresponding js file changed (131ms)
✓ reloads modules in development mode, if corresponding js file changed (113ms)
✓ supports CORS - CrossOriginResourceSharing by config (allowedOrigins)
✓ sends CSP - Content Security Policy header, if configured
✓ offers req.cookies and req.cookiesByArray object, if there are cookies
compose, if validation of a component fails
✓ should skip corresponding component and response with status 500
compose, if environment is in PRODUCTION mode
✓ does not load changed module’s script file and shows same result as before
component router
✓ routes requests based on component config
✓ support different methods for same path
✓ checks accept header
✓ checks http method
✓ checks http method before login
dev-mode: synchronous reload for @scope-a/component-one
dev-mode: synchronous reload for ./js/index
✓ responses "not found" for other paths
✓ requires authentication, if component or function is not public
✓ invokes a "login" promise for private resources
✓ removes passwort after login, for security reasons
✓ invokes a "resources" promise, if configured
✓ invokes a "resources" promise, if configured and use config data
✓ it catches up failed "resources" promise
✓ supports error handler middleware
✓ supports Content-Type: application/x-www-form-urlencoded
✓ supports to turn off "urlencoded" middleware endpoint specific
Cross Site Request Forgery protection
✓ response a session-secret cookie, based on config)
✓ serves 403, if token is invalid
✓ accepts valid token send by form hidden field "_csrf"