IS IN DEV-MODE: USE IT AT YOUR OWN RISK
Mii-js is NodeJS, MVC, Framework, Inspired By Yii-2
Yii-2 Framework: Yii-2
NPMJS: Mii-js
GitHub : Mii
License: MIT License
Dependencies [ node-js ]
package | min. version | required |
---|---|---|
cookie-parser | v 1.4.3 | yes |
events | v 3.0.0 | yes |
express | v 4.16.3 | yes |
express-session | v 1.15.6 | yes |
helmet | v 3.13.0 | yes |
pretty | v 2.0.0 | yes |
querystring | v 0.2.0 | yes |
serve-static | v 1.13.2 | yes |
session-file-store | v 1.2.0 | yes |
compression | v 1.7.3 | no |
csurf | v 1.9.0 | no |
hpp | v 0.2.2 | no |
mysql | v 2.16.0 | no |
morgan | v 1.9.1 | no |
debug | v 4.0.1 | no |
nodemailer | v 4.6.8 | no |
install
$ npm i mii-js --save
MiiHelper
MiiHelper will help create basic app structure with simple bootstrap based page and controllers / views
Examples:
$ node ./MiiHelper --create --app-root ./sites/my-new-site.com
// or
$ node ./MiiHelper -c -r my-site.com
$ node ./MiiHelper -c -r ./any/path/to/my/my-site.com
output
[06:04:54][O] : # MiiHelper
[06:04:54][O] : # Mii. newProject: creating app_root
[06:04:55][O] : # Mii: newProject: created ..
[06:04:55][O] : To start:
[06:04:55][O] : cd my-new-site.com
[06:04:55][O] : node ./app.js
Start new created App
$ cd my-new-site.com
$ node ./app.js
output
toor@abi:abc.com$ node ./app.js
[09:23:52][O] : # readConfig: [./configs/main.json]
[09:23:52][O] : # Mii::Express: serveStatic:[ public_html ]
[09:23:52][O] : # Mii::Security: dnsPrefetchControl:[ true ]
[09:23:52][O] : # Mii::Security: frameguard:[ true ]
[09:23:52][O] : # Mii::Security: hidePoweredBy:[ true ]
[09:23:52][O] : # Mii::Security: hsts:[ true ]
[09:23:52][O] : # Mii::Security: ieNoOpen:[ true ]
[09:23:52][O] : # Mii::Security: noSniff:[ true ]
[09:23:52][O] : # Mii::Security: xssFilter:[ true ]
[09:23:52][O] : # Mii::Security: HPP:[ true ]
[09:23:52][O] : # Mii::Server: compression:[ true ]
[09:23:52][O] : # Mii::Session: path :[ ./sessions ]
[09:23:52][O] : # Mii::Session: key :[ srf*********** ]
[09:23:52][O] : # Mii::Session: sameSite :[ true ]
[09:23:52][O] : # Mii::Session: httpOnly :[ true ]
[09:23:52][O] : # Mii::Session: secure :[ true ]
[09:23:52][L] :
[09:23:52][O] : # Mii::createServer: type: [http]
[09:23:52][O] : # Mii::createServer: type: [https]
[09:23:52][O] : # Mii::createServer: type: [http] => status: [200] => 127.0.0.1:8080
[09:23:52][O] : # Mii::createServer: type: [https] => status: [200] => 127.0.0.1:8433
[09:23:52][O] : # MySqlDB: Connected ...
[06:08:14][O] : # MySqlDB: Connected ...
Created App structure
├── app.js
├── cert
│ ├── 127.0.0.1.pem
│ └── 127.0.0.1.cert
│
├── configs
│ └── main.json
│
├── controllers
│ ├── Site.js
│ ├── MyClass.js
│ └── UpperCaseControllerName.js
│
├── components
│ └── MyComponent.js
│
├── layouts
│ ├── layout-template.html
│ └── main.html
│
├── runtime
│ └── logs
│
├── public_html
│ ├── css
│ ├── img
│ ├── robots.txt
│ └── vendor
│
├── sessions
└── views
└── site
└── index.html
Basic Controller
// camel-case-url-names => CamelCaseUrlNames
const BaseController = require('./mii/BaseController');
module.exports = class Site extends BaseController{
# site.com/
actionIndex( Mii ){
# [Mii] is reference to [this]
Mii.render({});
# or
this.render({});
}
# site.com/site/my-method/
actionMyMethod( Mii ){
}
}
const BaseController = require('./mii/BaseController');
module.exports = class MyClass extends BaseController{
# Not required, will be executed if available
before( Mii ){
# change default layout for all actions in current class
this.layout = 'main';
}
actionLogin( Mii ){
# change layout at runtime for given action
this.layout = 'main-2';
this.title = 'Users';
this.render({
users: ['Bob','Alice']
});
}
actionOther( Mii ){
try{
# ...
}catch( e ){
this.exception( e ); # Not required
}
}
}
Custom Components
app_root/components/MyComponent
const BaseComponent = require('./mii/BaseComponent');
module.exports = class MyComponent extends BaseComponent{
api( path,cb ){
this.Mii.httpGet({url:'https://api.binance.com/api/v3/'+path, json: true}, function( http_res ) {
( http_res.code == 200 ) ? cb( http_res.data ) : cb( [] );
});
}
sqrt(num) {
return Math.sqrt( num );
}
isPrime(num) {
for ( var i = 2; i < num; i++ )
if ( num % i === 0 )
return false;
return true;
}
getPrimes(upto) {
var arr = [2];
for ( var i = 3; i < upto; i+=2 )
if ( this.isPrime(i) )
arr.push(i);
return arr;
}
}
const BaseController = require('./mii/BaseController');
module.exports = class MyClass extends BaseController{
actionIndex( Mii ){
let MyComponent = Mii.getComponent('MyComponent');
let sqrt = MyComponent.sqrt(37);
let primes = MyComponent.getPrimes(10);
Mii.renderJson( {sqrt, primes} );
}
actionApiPrices( Mii ){
Mii.getComponent('MyComponent').api( 'ticker/price', function(res){
Mii.renderJson( res );
});
}
}
Controller Methods :: request and/or output JSON
const BaseController = require('./mii/BaseController');
module.exports = class MyClass extends BaseController{
# site.com/my-class/show-users/
actionShowUsers( Mii ){
# Will output pure JSON with header [application/json]
# Any layout will be ignored
this.renderJson([
{ Name: "Bob", role: "Admin"},
{ Name: "Alice", role: "User"},
]);
}
# site.com/my-class/api/type/my-type/id/32/?abc=123
actionApi( Mii ){
let id = Mii.getParam('id');
let type = Mii.getParam('type');
let abc = Mii.getGet('type');
this.httpGet({url "http://api.com/prices/type/"+type+"/id/"+id, json:true}, function(json_t){
if( json.code == 200 ){
// ...
}
// [Mii] is reference to [this]
Mii.renderJson( json );
});
}
}
Controller Methods :: Models && Post/Get/Ajax
const BaseController = require('./mii/BaseController');
module.exports = class MyClass extends BaseController{
# site.com/my-class/find-user/name/Tom/id/32/age/54/?lang=en
actionFindUser( Mii ){
# GET
let id = Mii.getParam('id');
let age = Mii.getParam('age');
let name = Mii.getParam('name');
let lang = Mii.getGet('lang');
Mii.model('user').find({id: id, name: name }, function( model ){
if( model.id ){
Mii.setSession( 'user_id', model.id );
model.set({
email:'ch3ll0v3k@yandx.com',
pwd: 'new-password',
role: 'new',
});
model.save( function(res){
Mii.renderJson( res );
});
}else{
# not found ?
Mii.renderJson( model );
}
});
}
# site.com/my-class/new-user/
actionNewUser( Mii ){
if( Mii.isPost() && Mii.isAjax() ){
# POST
let email = Mii.getPost('email');
let pwd = Mii.getPost('pwd');
let role = Mii.getPost('role');
# Model find data
Mii.model('user').find({email: email}, function( model ){
if( model ){
# User exists ...
return false;
}
pwd = Mii.hashPwd( pwd ); // F3E9D8-D9C9BC-CAB86B-9DA938-1FAD58
# Create new user
Mii.newModel('user', function( model ){
if( model ){
model.set({
email : email,
pwd : pwd,
role : role,
created: Mii.getUnixTime()
});
model.save( function(res){
Mii.renderJson( res );
});
}
});
}
}
}
}
Controller Methods :: Models && Options
const BaseController = require('./mii/BaseController');
module.exports = class MyClass extends BaseController{
# site.com/my-class/
actionIndex( Mii ){
# Havy data processing ...
Mii.setTimeout( 30 ); // max sec. exec. time (0 == disable)
Mii.setContentType('application/json');
Mii.cleanUp( "dangerous string <%> \"'/ -->\ß23' " )
Mii.toJson({"ABC", 123});
Mii.fromJson( '{"ABC", 123}' );
Mii.getUnixTime(); # 154543344
Mii.csrfToken(); # CSRF (if enabled in config file)
// GET / POST / URL-PARAM
// /site.com/site/index/name/Tom/?lang=en
Mii.getParam("name"); # "Tom" || false
Mii.getGet("lang"); # "en" || false
Mii.getGet(); # "_GET" || {}
Mii.getPost("key"); # "value" || false
Mii.getPost(); # "_POST" || {}
Mii.isPost(); # true || false
Mii.isAjax(); # true || false
Mii.isFile("./to/file"); # true || false
Mii.isDir("./to/dir"); # true || false
Mii.getHeaders(); # array with all headers
Mii.getHeader("HOST"); # "value" || false
Mii.setSession( key, val ); # true || false
Mii.getSession( key ); # "value" || false
// Add headers to output
Mii.setHeader('my-key', 'my-val');
Mii.httpGet({ url: 'http://site.com' json: true }, function(res){
res.headers # response headers
res.code # response status code
res.data # response data
res.msg # response status message
if( res.code == 200 ){
Mii.renderJson( res );
}
});
let params = {
url: 'http://127.0.0.1:8080/api/post',
data: { ABC:123, DEF:456 }, # not required
json: true, # not required
headers: { # not required
'Content-Length': 1024,
'My-Key': 'My-Val',
}
}
Mii.httpPost( params, function(res){
res.headers # resp. headers
res.code # status code
res.data # data
res.msg # status message
if( res.code == 200 ){
Mii.renderJson( {headers: res.headers, data: res.data} );
}else{
Mii.renderJson( res );
}
});
}
}
Controller Methods :: Models && Render
const BaseController = require('./mii/BaseController');
module.exports = class MyClass extends BaseController{
# site.com/my-class/
actionIndex( Mii ){
Mii.renderRaw('<H4>Hello</H4>');
Mii.renderRaw( {Name: "Tom"} );
Mii.renderJson( {Name: "Tom"} );
Mii.renderPartial( "./views/user/_part.html" );
Mii.render('view-name', {});
# Will get view according to [controller]/[action] name
Mii.render({
user: [
{name:"Bob"}, {name:"Alice"},
]
});
}
}
Controller Methods :: Sending mail [smtp options required => config]
const BaseController = require('./mii/BaseController');
module.exports = class MyClass extends BaseController{
actionIndex( Mii ){
let to = 'email@host.com';
let subject = 'Welcome mail';
let msg = ' Welcome <h4> {user-name} <h4> ';
Mii.newMail( {to, subject, msg} ).send(function( res ){
Mii.renderJson( res );
if( res.code == 200 ){
// ...
}else{
// ...
}
});
}
}
Controller Methods :: Models && DataBase
const BaseController = require('./mii/BaseController');
module.exports = class MyClass extends BaseController{
# site.com/my-class/
actionIndex( Mii ){
Mii.DB("select * from user", function(db_res){
if( db_res.code == 200 ){
Mii.render({ user: db_res.data });
}else{
Mii.renderRaw('<h5> User Not found ... </h5>');
}
});
}
}
Micro Template-Engine with basic logic
http://site.com/my-class/my-action/
const BaseController = require('./mii/BaseController');
module.exports = class MyClass extends BaseController{
actionMyAction( Mii ){
Mii.render({
balance: 124.45,
users: [
{name : 'Tom', gender : 'Male', age : 120 },
{name : 'Bob', gender : 'Male', age : 88 },
{name : 'Alica', gender : 'Female', age : 79 },
],
show_header: true,
show_footer: false,
show_users_list: true,
});
}
}
<html lang="<? $lang ?>">
<head>
<meta charset="<? $charset ?>"/>
<meta name="csrf-token" content="<? $_csrf ?>"/>
<title> <? $app_name ?> - <? $title ?> </title>
</head>
<body>
<div> Balance : <? echo( $balance ) ?> </div>
<div> Balance or : <?( $balance )?> </div>
<? if( $show_header ) ?>
<? include( "my-controller/_header" ) ?>
<? endif ?>
<? if( $show_users_list ) ?>
<div>
<h4> Users: <? echo ( $total_users ) ?> </h4>
<? foreach( $users as $user ){ ?>
<p>
Name: <? echo ( $user.name ) ?>,
Age: <? echo ( $user.age ) ?>,
Gender: <? echo ( $user.gender ) ?>
</p>
<? } ?>
</div>
<? endif ?>
<? if( $show_footer ) ?>
<? include( "my-controller/_footer" ) ?>
<? endif ?>
<? include( "my-controller/_description" ) ?>
// Or from other view directory
<? include( " other-controller/_other_view" ) ?>
</body>
</html>
App Config ./configs/main.json
{
"site": {
"host": "site.com",
"url": "https://site.com",
"name": "Mii",
"lang": "en",
"charset": "utf-8",
"version": "1.0.1"
},
"mii": {
"paths": {
"configs": "configs",
"controllers": "controllers",
"components": "components",
"layouts": "layouts",
"views": "views",
"public_html": "public_html"
}
},
"logs": {
"log": true,
"root": "runtime/logs",
"file_size_mb": 1
},
"runtime": {
"debug": true,
"morgan": false,
"onError": {
"controller": "Site",
"action": "Error"
}
},
"server": {
"ip": "127.0.0.1",
"host": "127.0.0.1",
"timeout": 5,
"compression": true,
"time-zone": "+1",
"http": {
"port": 8080
},
"https": {
"port": 8433
},
"headers": [{
"X-Powered-By": "Mii-js"
}],
"document_root": "public_html"
},
"security": {
"CSRF": {
"enabled": false,
"secret": "some-secret",
"key": "_csrf",
"sameSite": false,
"httpOnly": true,
"domain": "*"
},
"HPP": {
"enabled": true
},
"helmet": {
"contentSecurityPolicy": false,
"descr: contentSecurityPolicy": "for setting Content Security Policy, default: [false]",
"expectCt": false,
"descr: expectCt": "for handling Certificate Transparency, default: [false]",
"dnsPrefetchControl": true,
"descr: dnsPrefetchControl": "controls browser DNS prefetching, default: [true]",
"frameguard": true,
"descr: frameguard": "to prevent clickjacking, default: [true]",
"hidePoweredBy": true,
"descr: hidePoweredBy": "to remove the X-Powered-By header, default: [true]",
"hpkp": false,
"descr: hpkp": "for HTTP Public Key Pinning, default: [false]",
"hsts": true,
"descr: hsts": "for HTTP Strict Transport Security, default: [true]",
"ieNoOpen": true,
"descr: ieNoOpen": "sets X-Download-Options for IE8+, default: [true]",
"noCache": false,
"descr: noCache": "to disable client-side caching, default: [false]",
"noSniff": true,
"descr: noSniff": "to keep clients from sniffing the MIME type, default: [true]",
"referrerPolicy": false,
"descr: referrerPolicy": "to hide the Referer header, default: [false]",
"xssFilter": true,
"descr: xssFilter": "adds some small XSS protections, default: [true]"
}
},
"session": {
"name": "server-session-cookie-id",
"secret": "super-secret-stuff",
"saveUninitialized": true,
"resave": true,
"path": "./sessions",
"cookie": {
"key": "_csrf",
"sameSite": true,
"httpOnly": true,
"secure": true
}
},
"cache": {
"controllers": false,
"files": false,
"components": false,
"modules": false
},
"ssl": {
"keys": {
"key": "cert/127.0.0.1.key",
"cert": "cert/127.0.0.1.pem"
}
},
"db": {
"mysql": {
"connection": {
"host": "localhost",
"port": 3306,
"user": "root",
"password": "toor",
"database": "Mii"
}
},
"redis": {},
"mongo": {}
},
"memcached": {
"secret": "memcached-secret-key",
"key": "test",
"proxy": "true",
"hosts": ["127.0.0.1:12345"]
},
"smtp": {
"host": "smtp.yandex.ru",
"port": 465,
"secure": true,
"auth": {
"user": "user@mail-server.com",
"pass": "my-password"
}
},
"app_root": " will be set at runtime ",
"mii_root": " will be set at runtime "
}