interlayer v0.13.4
interlayer
At this point, the server version is still in alpha. You can suggest an idea or report an error here: New issue
The stable version of the server will be implemented after writing all the necessary features and tests to them.
Changelog
Features
- Serving Static Content
- Upload files
- Auto-reload server on file change (reload on new files not supported)
- Clusterization
- Postgres\mysql\redis built-in DAL's for data storage
- Mailgun\sparkpost\smtp build-in mail sender packages
- Localization
- WebScoket
Install
npm install --save interlayer
or
yarn add interlayer
Project tree example
- /node_modules/
- package.json
- /files/
- this folder will be served by config.serve
- index.html
- style.css
- script.js
- /images/
- logo.jpeg
- index.js
- /i18n/
- en-US.json
- /middleware/
- auth.js
- /modules/
- weather.js
- config.json
Request processing steps
- request preparation - filling with functions and objects, see here
- search for a module. if not found - search for a file in
config.serve
and give it to the client - if the module is found, parse the data if the request method is POST
- for all matching triggers run middlewares, more see here
- if the found module has a prerun in its meta, run it, more see here
- finally launch our module(that can use any data from here), in response we wait for see here
- convert to json text if meta.toJson || meta.contentType == 'json' || headers'Content-Type' == 'application/json'
- done
/modules/weather.js example
exports._obtain = {
checkIp: true,
prerun(request, moduleMeta, cb){
}
};
exports.obtain = (request, cb){
cb(null, 'good weather');
};
/middleware/auth.js example
exports.triggers = {
'meta.checkIp': (request, moduleMeta, cb) => {
if(request.ip === '127.0.0.1'){
return cb('bad ip');
}
cb();
},
'request.params.city': (request, moduleMeta, cb) => {
if(request.params.city === 'london'){
return cb('bad weather');
}
cb();
}
}
Variants to start server
const config = {
port: 80,
serve: ['files']
};
require('interlayer')(config);
or
require('interlayer')('config.json');
or
const server = require('interlayer').server();
server
.setRootPath(__dirname)
.loadConfigFile('config.json')
.start()
config
object or config.json
file configuration
Avaliable params:
Avaliable properties in config
object or config.json
file
Property | Default | Type | Description |
---|---|---|---|
port | 8080 | Number | Web server port number |
secure | --- | Object | SSL configuration object with paths to files: {key:'',cert:''} |
initPath | ./ | String | Web server root path |
logPath | ./ | String | Path to create the logs.log file |
timeout | 60(sec) | Number | Timeout in seconds, then user will see {error: 'TIMEOUT'} Note, execution of the method is not interrupted |
workers | 1 | Number | Number of instances for load balancing. If number more than 1, uses node.js cluster |
restartOnChange | false | Boolean | Flag determine is server will restart automatically when files in the folder with modules was changed |
useDals | --- | ObjectdalName = dalConfig | The configuration object for dal modules to be used. Supports redis(redis:{} ), mysql(mysql:{} and default will be {host: '127.0.0.1',user: 'root'} ), postgress(postgress:{} and default will be {host: '127.0.0.1',user: 'root'} ). For config built-in redis see here🌍, mysql see here🌍, postgres see here🌍 (Example of dal's config: useDals: {mysql: {port: 6375, user: 'admin'}, redis: {}} ) |
useEmailSenders | --- | ObjectemailSenderName = emailSenderConfig | The configuration object for the mail senders to be used. Supports mailgun(mailgun:{} ), sparkpost(sparkpost:{} ), smtp(smtp:{} ). For config built-in mailgun see here🌍, sparkpost see here🌍, smtp see here🌍 |
serve | --- | Array[Strings[]] | An array folders to serve. Priority over the last folder |
modules | ./modules | Array[Strings[]] | An array folders with modules. Priority over the last folder. (Default directory is './modules' unless otherwise specified.) How to create |
views | ./files | Array[Strings[]] | An array of folders with files, which you can be uses as templates, or returned through the api(by using request.getView). Priority over the last folder. (Default directory is './files' unless otherwise specified.) |
i18n | ./i18n | Array[Strings[]] | An array of folders with localization files. Priority over the last folder. (Default directory is './i18n' unless otherwise specified.) How to create |
dals | --- | Array[Strings[]] | An array of folders with your dal(Data Access Layer) modules. Priority over the last folder. How to create |
emailSenders | --- | Array[Strings[]] | An array of folders with your email senders. Priority over the last folder. How to create |
middleware | --- | Array[Strings[]] | An array of folders with middlewares. Priority over the last folder. How to create |
middlewareOrder | --- | Array[Strings[]] | An array with ordered names of middlewares |
middlewareTimeout | 10(sec) | Number | Timeout in second, then user will see {error: 'TIMEOUT'} Note, execution of the runned middlewares is not interrupted |
skipDbWarning | false | Boolean | Skip warning in console if useDals not defined in config |
defaultHeaders | --- | ObjectheaderName = headerValue | An object with default headers, which have to be added to the every response |
debug | false | Boolean | Allow to display log.d in console and add to the logs.log file |
instantShutdownDelay | 1500(ms) | Number | Delay in milliseconds after server will shutdown on process SIGINT or SIGTERM signal, or process message: shutdown |
retryAter | 10(sec) | Number | Time in seconds for Retry-After response header with server HTTP 503 status. Works until instantShutdownDelay |
noDelay | true | Boolean | Flag to enable/disable Nagle algoritm for all connections. See here🌍 |
websocket | --- | Boolean/Object | Start websocket. If true then on the same port as server, except as stated in the Object. See here🌍. Initialized server instance can be found in initFunction simpleRequest.websocket |
useHttpErrorFiles | false | Boolean | Possible errors will be given as files if they are found in directories specified in addViewPath |
skipParsePost | false | Boolean | Skip parse POST |
formidableOptions | {} | Object | Set formidable options on parse data when headers'content-type' not null, list of options see here🌍 |
startInits | true | Boolean | Start functions that were added via Module app.setInit |
disableLogFile | false | boolean | Disable to write log file, the console.log and others continues to be written by the console |
Intrlayer instance configure
let serverInstance = require('interlayer').server();
How to use
let server = require('interlayer').server();
server.setRootPath(__dirname);
server.loadConfigFile('config.json');
server.start();
Avaliable methods:
Property | Default | Type | Description |
---|---|---|---|
start(configObject / null) | --- | Object | Starting the server with/without the configuration object |
loadConfigFile(path) | --- | String | Initializing configuration from file |
setConfig(configObject) | --- | Object | Setting a configuration from an object |
getConfig(configObject) | --- | Object | Get the resulting configuration |
setRootPath(path) | ./ | String | Set root directory |
setLogPath(path) | ./ | String | Set a directory of the log file |
setPort(port) | 8080 | Number | Set the server port |
setSecure(secureObject) | --- | Object | SSL configuration object with paths to files: {key:'',cert:''} |
setWorkersCount(workerNumber) | 1 | Number | Number of instances for load balancing. If number more than 1, uses node.js cluster |
setTimeout(timeout) | 60(sec) | Number | Timeout in seconds, then user will see {error: 'TIMEOUT'} Note, execution of the method is not interrupted |
setDefaultHeaders(headersObject) | --- | Object | An object with default headers, which have to be added to the every response |
setRestartOnChange([true / false]) | false | Boolean | Boolean value determine is server will restart automatically when files in the folder with modules was changed |
setSkipDbWarning([true / false]) | false | Boolean | Skip warning in console if useDals not defined in config |
setDebugMode([true / false]) | false | Boolean | Allow to display log.d in console |
setNoDelay([true / false]) | true | Boolean | Flag to disable/enable Nagle algoritm for all connections. See here🌍 |
setInstantShutdownDelay(timeout) | 1500(ms) | Number | Delay in milliseconds after server will shutdown on process SIGINT or SIGTERM signal, or process message: shutdown |
setRetryAter(timeout) | 10(sec) | Number | Time in seconds for Retry-After response header with server HTTP 503 status. Works until config.instantShutdownDelay |
addEmailSender(emailSenderName, emailSenderConfig) | --- | String, Object | Add an email sender. Priority over the last folder. How to create |
addDalPath(path, [path, [path]]) | --- | String | Add path to DAL's(Data Access Layer) modules. Priority over the last added path |
addDal(dalName, dalConfig) | --- | String, Object | The configuration(dalConfig) for dal module(dalName) to be used. Out of the box is available redis(for use specify redis, {} ), mysql(for use specify mysql, {} and default dalConfig will be {host: '127.0.0.1',user: 'root'} ), postgress(for use specify postgress, {} and default dalConfig will be {host: '127.0.0.1',user: 'root'} ). For configure redis see here🌍, mysql see here🌍, postgres see here🌍 |
addMiddlewarePath(path, [path, [path]]) | --- | String, ... | Add path to middleware modules. Priority over the last added path. How to create |
setMiddlewareOrder(middlwareName, middlwareName) | --- | String or ArrayStrings | An array(or arguments) with ordered names of middlewares |
setMiddlewareTimeout(timeout) | 10(sec) | Number | Timeout in second, then user will see {error: 'TIMEOUT'} Note, execution of the runned middlewares is not interrupted |
addModulesPath(path, [path, [path]]) | ./modules | String, ... | Add path to modules. Priority over the last added path. (Default directory is './modules' unless otherwise specified.) How to create |
addI18nPath(path, [path, [path]]) | ./i18n | String, ... | Add path to localization files. Priority over the last added path. (Default directory is './i18n' unless otherwise specified.) How to create |
addServePath(path, [path, [path]]) | --- | String, ... | Add path to Serving Static Content. Priority over the last added path |
addViewPath(path, [path, [path]]) | ./files | String, ... | Folders with files, which you can be uses as templates, or returned through the api(by using request.getView ). Priority over the last folder. (Default directory is './files' unless otherwise specified.) |
setWebsocketConfig(websocket) | --- | Boolean/Object | Start websocket. If true then on the same port as server, except as stated in the Object. See here🌍. Initialized server instance can be found in initFunction simpleRequest.websocket |
setUseFilesAsHTTPErrors([true / false]) | false | Boolean | Possible errors will be given as files if they are found in directories specified in addViewPath |
setSkipParsePost([true / false]) | false | Boolean | Set skip parse POST |
setFormidableOptions({}) | {} | Object | Set formidable options on parse data when headers'content-type' not null, list of options see here🌍 |
disableInits([true / false]) | true | Start functions that were added via Module app.setInit | |
disableLogFile([true / false]) | false | boolean | Disable to write log file, the console.log and others continues to be written by the console |
Module creation
Example of modules/myModule.js
const app = require('interlayer').module();
exports.module = app;
let log = app.getLog('myModuleId');
app.setMeta({asJson: true});
app.setInit((request, requestCallback)=>{
log.i('Module inited');
requestCallback();
});
app.addMethod('myMethod', {toJson: true}, (request, requestCallback)=>{
let fullLog = request.modifyLog(log);
log.i('I am log without requestId but with myModuleId');
request.log.i('I am log with requestId but without myModuleId');
fullLog.i('I am log with requestId and with myModuleId');
requestCallback(null, {ok: true}, 200, {}, false);
});//Could be called in the path of /myModule/myMethod
Avaliable app methods
const app = require('interlayer').module();
Method | Property types | Description |
---|---|---|
getLog(name) | String | Get the object to output messages to the console. Object of {i:funcion, e:function, d: function, w: function, c: function} type |
setMeta(metaObject) | Object | Set the default parameters for all methods of this module. metaObject |
setInit(initFunction) | Function | Set the function to initialize the module at server start. initFunction |
addMethod(methodUrl, [methodMeta,] methodFunction) | String, Object, Function | Adds a new method with/without info(meta). methodMeta and methodFunction |
add(methodUrl, [methodMeta,] methodFunction) | String, Object, Function | Alias for addMethod |
setMethodInfo(methodUrl, methodMeta) | String, Object | Sets info(meta) for method. methodMeta |
info(methodUrl, methodMeta) | String, Object | Alias for setMethodInfo |
getMethod(methodUrl) | String | Returns the method function |
getMethodInfo(methodUrl, [withGlobalMeta]) | String, Boolean | Returns method info(meta) |
initFunction(simpleRequest)
simpleRequest.url
- Empty stringsimpleRequest.headers
- Empty objectsimpleRequest.DAL
- DAL objects if initialisedsimpleRequest.config
- Configuration objectsimpleRequest.websocket
- websocket server instanse if initialised
... and functions as in methodFunction
request
except getResponse
, getRequest
and other http request methods See here🌍
metaObject and methodMeta default paramerets
Key | Type | Description |
---|---|---|
default = methodFunction | Function | Module(not method) function, can be used to output HTML, Available at /moduleUrl . Only for metaObject. methodFunction |
html = methodFunction | Function | Same as default . Only for metaObject |
find = methodFunction | Function | The method search function is only triggered if no other methods have been processed. Only for metaObject. See methodFunction |
path | String | Changes methodUrl to path |
addToRoot | Boolean | Skip moduleUrl and use methodUrl or path as url to method |
alias | String | Alias path to method |
timeout | Number | Seconds until HTTP 408(Request timeout) |
noDelay | Boolean | Disable/enable the use of Nagle's algorithm. See here🌍 |
middlewareTimeout | Number | Timeout in second, then user will see {error: 'TIMEOUT'} Note, execution of the runned middlewares is not interrupted |
prerun = prerunFunction | Function | Function or link to function which will be runned before method. May be usefull for preparing request. prerunFunction |
toJson | Boolean | Convert response to JSON string |
contentType | String | Same as toJson if contentType==json |
skipRequestLog | String | Skip request log output |
hidden | Boolean | Skip method from return information while calling request.getMethodsInfo |
skipParsePost | Boolean | Skip parse POST |
prerunFunction(request, moduleMeta, requestCallback)
request
- same as in methodFunction
methodFunction(request, requestCallback):
request
:
| Methods | Property types | Description |
| --- | --- | --- |
| modifyLog(log)
| Object | Add to log object requestId for log created with global.logger |
| getView(file, callback)
| String, Function | Return file
in callback
from paths specified in config.views
or in server.setViewPath()
. callback = (error, data)
|
| getViewSync(file)
| String | Synchronous request.getView
|
| getFile(file, callback)
| String, Function | Return file as is. callback = (error, data, {'Content-type':''})
|
| addCookies(key, value)
| String, String | Set coockie for response |
| rmCookies(key)
| String | Remove coockie from responce |
| i18n(key[, defaultValue])
| String, String | Return translate for key
or return defaultValue
|
| obtainI18n()
| --- | Return object with languages |
| getMethodsInfo(showHidden)
| Boolean | Returns all methods (except hidden methods if showHidden is not specified) |
| lockShutdown()
| --- | Blocks the termination of the process until the request is completed |
| unlockShutdown()
| --- | Unlock the termination of the process |
| getResponse()
| --- | Returns the original responce |
| getRequest()
| --- | Returns the original request |
| error(text)
| String | Returns 503 http code |
| end(text[, code[, headers[, type]]])
| String[, Number[, Object, String]] | Returns code
http code with text
(as binary if type==bin
) and headers
|
request
:
| Property | Type | Description |
| --- | --- | --- |
| config
| Object | An object of configuration specified at start of server |
| ip
| String | Client ip adress |
| url
| String | Request url |
| path
| String | Request path(module/method) |
| method
| String | Uppercased type of request - POST|GET|... |
| isPost
| Boolean | true|false |
| params
| Object | An object of parsed GET params |
| post
| Object | An object of parsed POST params(with formidable🌍) |
| files
| Object | An object of uploaded files(with formidable🌍) |
| cookies
| Object | An object of parsed cookies |
| headers
| Object | An object of request headers |
| DAL
| Object | An object with DALs, which was initialized by config.useDals
or server.addDal()
|
| mail
| Object | An object with mail senders, which was initialized by config.useEmails
or server.addEmailSender()
|
| id
| String | requestId |
| log
| Object | The same as global.logger.create(moduleID)
, but with requestID included(not include moduleID) |
| helpers
| Object | requiest.helpers |
request.helpers
Methods | Property types | Description | |
---|---|---|---|
helpers.generateId() | --- | Geneate 8-character identifier(a-zA-Z0-9) | |
helpers.toJson(obj) | * | Convert obj to JSON string | |
helpers.clearObj(obj, toRemove) | Object, Array | Delete parameters of obj from toRemove array of strings | |
helpers.isBoolean(val) | * | Check is val string is Boolean(true | false) |
helpers.JSV(json, schema, envId) | Object, Object, String | See here🌍. Create environment with envId and call validate with json and schema | |
helpers.mime() | Object | return mime type by file extension or fallback or 'application/octet-stream' |
requestCallback(error, data, httpCode, responseHeaders, isBinary)
error
- null or undefined or String or Objectdata
- null or String or Object or Binary(ifisBinary
= true)httpCode
- null or Number See here🌍responseHeaders
- null or Object See here🌍 If Content-Type = application/json thendata
will be returned as JSONtype
- null or 'bin'. If 'bin' thendata
will be returned as Buffer
Global objects added
global.logger
Object to create log
with global.logger.create(logName) or global.logger(logName)
were logName
is String.
log
avaliable methods:
| Methods | Description |
| --- | --- |
| i
| Parameters same as for console.log
. See here🌍 |
| w
| Parameters same as for console.warn
. See here🌍 |
| e
| Parameters same as for console.error
. See here🌍 |
| d
| Parameters same as for console.debug
. See here🌍 |
| c
| Parameters same as for console.log
. See here |
Note that this type of logging don't allow to track the request id.
To have ability track the request id use the request.modifyLog
method:
let log = global.logger.create('moduleID');
exports.myMethod = (request, cb)=>{
let log = request.modifyLog(log);
}
global.intervals
Object with methods:
| Methods | Property types | Description |
| --- | --- | --- |
| add(function, timeout)
| Function, Number | Return key
|
| add(function, timeout = {year: '*', month: '*', date: '*', day: '*', hour: '*', minute: '*', second: '*'})
| Function, Object | Any of the parameters can be "*"
or "2020,2040"
- the options are listed in commas. Return key
|
| del(key)
| String | Remove by key
|
| disable(key, flag)
| String, Boolean | Disable/Enable interval by key
and flag
|
| enable(key)
| String | Enable interval by key
|
The startup interval is every second. If the start conditions (timeout
) match, function
is called with a parameter as a function to delete the interval - similar call to global.intervals.del(key)
.
Often used inside the function initFunction
Remember, if at server startup config.startInits = false
or disableInits(false)
then functions added via setInit(initFunction)
will not be added to global.intervals
Features
Logging:
const log = global.logger('moduleID');
log.i(); // Usual log - displayed in green
log.w(); // Warn - displayed in yellow
log.e(); // Error - displayed in red
log.c(); // Critical error - displayed in white
Note that this type of logging don't allow to track the request id.
To have ability track the request id use the request.modifyLog
method:
const log = global.logger('moduleID');
exports.myMethod = (request, cb)=>{
let log = request.modifyLog(log);
}
Or use the request.log
instead, if 'moduleID'(identifier specified in global.logger.create
function) not required.
let log = global.logger.create('moduleID');
exports.myMethod = (request, cb)=>{
let log = request.log;
}
---
Use dals:
request.DAL.redis.get('somekey', (err, data) => {
if(err){
request.log.e('redis.get somekey', err);
return cb(err);
}
...
});
request.DAL.mysql.query('SELECT * FROM users WHERE login = ?', ['admin'], (err, data, fields) => {
if(err){
request.log.e('mysql.query', err);
return cb(err);
}
})
Use email senders
request.mail.mailgun.send({}, callback) -> see params here https://documentation.mailgun.com/api-sending.html#sending
request.mail.sparkpost.send({}, callback) -> see params here https://developers.sparkpost.com/api/transmissions.html#header-transmission-attributes
//or use initialized senders as you want
request.mail.mailgun.client -> https://www.npmjs.com/package/mailgun-js
request.mail.sparkpost.client -> https://www.npmjs.com/package/sparkpost
Create dal
Example of dals/nameofdal.js
Then you can add nameofdal
to config.useDals
array (ex: config.useDals = {nameofdal: {...config}};
)
// init is not required
exports.init = (config, dalConfig) => {
};
// but methods is required
exports.methods = {
get: () => {},
set: () => {}
}
Create email sender
Example of emailSenders/nameofsender.js
Then you can add nameofsender
to config.useEmailSenders
array (ex: config.useEmailSenders = {nameofsender: {...config}};
)
// init is required
exports.init = (config, emailConfig) => {
};
// send is required
exports.send = (email, cb)=>{
}
Create middleware
Example of middleware/session.js
exports.triggers = {
'meta.checkSessions': 'checkSession', // you can specified string - name of function exported in module
'request.params.test': exports.test // also you can specified function or method of object
};
// but methods is required
// request context and callback described in module
exports.checkSession = (request, moduleMeta, cb) => {
};
exports.test = (request, moduleMeta, cb) => {
};
or
exports.run = (request, moduleMeta, cb)=>{
};
// this creates
// exports.triggers = {
// '*': exports.run
// };
Localization
Example of i18n/en.js
Note! You have to use double quotes, instead single quotes, because it's json file These are the actual keys used for error output.
{
"Not found": "Nothing found, 404, Bill Gates site",
"<center>Error 404<br>Not found</center>": "<h1>404 HTTP error.<h1>Not found</center>",
"Service Unavailable. Try again another time.": "503 HTTP error."
}
10 days ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
12 months ago
12 months ago
12 months ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago