interlayer
v0.13.7
Published
Minimalistic and fast ES6 Node.js web server with file monitor
Downloads
90
Maintainers
Readme
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
| --- | Object[dalName] = 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
| --- | Object[emailSenderName] = 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
| --- | Object[headerName] = 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 Array[Strings] | 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."
}