@pubcore/node-composition
Version:
composition of components served by express
204 lines (180 loc) • 6.46 kB
Markdown
## 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:{
'/todo-list':{
public: true,
context_path: '/todo'
},
'/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('/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](https://expressjs.com/en/guide/using-middleware.html):
1. 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
},
]
}
```
2. 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 -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"
```
#### References
[CQRS protection](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html)
["__Host-" cookie prefix](https://tools.ietf.org/html/draft-west-cookie-prefixes-05)