An object-oriented efficient MVC and REST framework

Getting started

Install the program

You can installCandyJstwo ways

  1. Usenpm
  2. Download the source code for manual deployment

Usenpminstall

npm install candyjs

Download the source code for manual deployment

ThroughGitHubdownload source code and place it to the node_modules directory
Note that the source code downloaded by github may need to remove the git repository information from it otherwise the npm installs other packages may be problematic

Run the program for the first time

This example outputs a Hello word program

First need to create a basic application framework as follows

InCandyJsthebindirectory provides a tool for creating the application_candy

IfCandyJsis installed bynpmthen_candywill be installed to thenode_modules/.bindirectory

IfCandyJsmanually installed by downloading the source code then need to find the tool in thenode_modules/candyjs/bindirectory

./node_modules/.bin/_candy PROJECT_NAME

This will create a simple application as follows


PROJECT_NAME
|
|- index.js
|
|- app
|  |
|  |-- controllers
|      |
|      |-- index
|      |   |
|      |   |-- IndexController.js
|      |
|   -- views
|      |
|      |-- index
|      |   |
|      |   |-- index.html
    

Go to the application directory to start the program

node index.js

Access

http://localhost:8090

Application structure

An application directory structure is as follows


PROJECT_NAME
|
|- index.js
|
|- node_modules
|
|- public
|
|- app
|  |
|  |-- controllers
|      |
|      |-- user
|      |   |
|      |   |-- IndexController.js
|      |   |-- OtherController.js
|      |
|      |-- goods
|      |   |
|      |   |-- IndexController.js
|      |   |-- OtherController.js
|      |
|   -- views
|      |
|      |-- user
|      |   |
|      |   |-- index.html
|      |   |-- other.html
|      |
|   -- goods
|      |   |
|      |   |-- index.html
|      |   |-- other.html
|      |
|   -- modules
|      |
|      |-- reg
|      |   |
|      |   |-- controllers
|      |   |   |
|      |   |   |-- IndexController.js
|      |   |
|      |   |-- views
|      |   |   |
|      |   |   |-- index.html
|      |   |
|      |   |-- other dir
|      |
|   -- runtime
|

Entry scriptindex.js

The entry script is the first loop in the application startup process. There is only one entry script that contains the startup script that will listen to the client's connection

The entry script basically does the following

  • Load the application configuration
  • Start the application
  • Register all the required components


var CandyJs = require('candyjs');

new CandyJs({
    'id': 1,
    
    'debug': true,
    
    // application path
    'appPath': __dirname + '/app',
    
    // register modules
    'modules': {
        'bbs': 'app/modules/bbs'
    },
    
    // log setting
    'log': {
        'targets': {
            'file': {
                'class': 'candy/log/file/Target'
            }
        }
    }
    
}).listen(8090, function(){
    console.log('listen on 8090');
});

Application

Applications are objects that govern the overall structure and lifecycle ofCandyJsapplication systems. Usually a framework contains two applications theWeb Applicationand theConsole ApplicationbutCandyJsonly has aWeb Application

Application properties

Various parameters can be introduced in the entry file. These parameters will eventually be assigned to the application object

Required properties
  • candy/web/Application.idThis property is used to identify the only application

  • candy/web/Application.appPathThis property used to specified application directory

Important attributes
  • candy/web/Application.routesMapUsed to define the routing handler

    
    'account': {
        'class': 'app/controllers/user/IndexController',
        'property': 'value'
    }
    

  • candy/web/Application.modulesTo register application module

    
    // register a bbs module
    'modules': {
        'bbs': 'app/modules/bbs'
    }
    

  • candy/web/Application.encodingProject encoding

  • candy/web/Application.debugWhether debugging is on

Custom properties

Other parameters passed in the entry file are passed to the application as a custom parameter

Application controller

The controller is part of theMVCschema that inherits the object ofcandy/core/Controllerclass responsible for handling the request and generating the response

Action

The controller consists ofactionsthat is the most basic unit for executing the end user's request. A controller has and only one entry action is calledrun


'use strict';

var CandyJs = require('candyjs');
var Controller = CandyJs.Candy.include('candy/web/Controller');

class IndexController extends Controller {

    run(req, res) {
        res.end('hello');
    }

}

module.exports = IndexController;

Action aspect

If the controller inherits fromcandy/web/Controller Then you can use the action aspect in the controller before or after action to perform some business logic


'use strict';

var CandyJs = require('candyjs');
var Controller = CandyJs.Candy.include('candy/web/Controller');

class IndexController extends Controller {

    beforeActionCall(req, res) {
        console.log('beforeActionCall')
    }

    afterActionCall(req, res) {
        console.log('afterActionCall')
    }

    run(req, res) {
        res.end('hello');
    }

}

module.exports = IndexController;

Note that there may be asynchronous operations in the project code. afterActionCall()does not guarantee execution after run() execution is complete

Routing and controller

Generally a route corresponds to a controller

[route_prefix]/[controllerId]

If the controller belongs to the module then the routing format is as follows

[moduleId]/[controllerId]

Controller search ordermodule controller --> common controller

Model

The model is part of theMVCpattern that represents the object of the business data

TemporarilyCandyJsdoes not provide a class to read the database

View

The view is part of theMVCpattern that is used to present the page to the end user

The view class typically combines the data provided by the model layer with the static page to generate a final page display to the user. CandyJstemporarily provides only a limited API at the controller level

Template engine

CandyJsdoes not implement a template engine. User needs to use the existing template engine to implement his own business

Controller level view API

If the user's controller inherits from thecandy/web/Controller then can use thegetView()method in the controller to get the view class instance

The view class provides the following API for user to use

  • getTemplateFilePath(view)Used to get absolute path of a view file
  • getTemplate(view, callback)Used to read the content of a view file
  • getTemplateFromPath(path, callback)Used to read the contents of a view file from the specified path


'use strict';

var CandyJs = require('candyjs');
var Controller = CandyJs.Candy.include('candy/web/Controller');

class IndexController extends Controller {
    
    run(req, res) {
        this.getView().getTemplate('index', (err, str) => {
            res.end(str);
        });
    }
    
}

module.exports = IndexController;

Module

Module is independent software unit. It consists ofModel View Controllerand other necessary components

Note that unlike the common project directory, the controllers and views in the module do not have a subdirectory

Create a module

In themodulesdirectory create a separate directory eg. bbs


modules
    |
    |-- bbs
    |   |
    |   |-- controllers
    |   |   |
    |   |   |-- IndexController.js
    |   |
    |   |-- views
    |   |   |
    |   |   |-- index.html
    |   |
    |   |-- other directory

Register the module

The module created can not be recognized by the system, we need to manually register


var CandyJs = require('candyjs');

new CandyJs({
    ...
    
    // register module bbs
    'modules': {
        'bbs': 'app/modules/bbs'
    },
    
    ...
    
}).listen(8090, function(){
    console.log('listen on 8090');
});

Component & Behavior

Component

Component is the base class ofProperty Behavior and Event

Components are instances ofcandy/core/Controller, or an extended class

Behavior classes are typically used in conjunction with component classes

Behavior

Behavior is instances ofcandy/core/Behavior, or an extended class

A behavior class can be used to enhance its functionality without changing the original component code

When a behavior is attached to a component, it will inject its methods and properties into the component and then access them as if they were to access the component's own methods and properties

The behavior class also can listen to the component's events and respond

Properties

Membership variables for the javascript class are also known as properties

Event

CandyJsimplements an observer pattern


'use strict';

var CandyJs = require('candyjs');
var Controller = CandyJs.Candy.include('candy/web/Controller');

class IndexController extends Controller {
    
    constructor(context) {
        super(context);
        
        this.on('myevent', function() {
            console.log('myevent fired');
        });
    }
    
    run(req, res) {
        this.trigger('myevent');
        
        res.end('hello');
    }
    
}

module.exports = IndexController;

Use behavior

Define behavior

Create a class by inheritingcandy/core/Behavioror its subclasses


'use strict';

var CandyJs = require('candyjs');
var Behavior = CandyJs.Candy.include('candy/core/Behavior');

class MyBehavior extends Behavior {
    constructor() {
        super();
        
        this.props1 = 1;
        this.props2 = 2;
    }
    
    myFun() {
        // todo
    }
}

module.exports = MyBehavior;

The above code defines the behavior classMyBehaviorand provides two propertiesprop1 prop2and a method for the component to attach the behaviormyFun()

Attach behavior to component

You can attach a behavior to a component either statically or dynamically

To attach a behavior statically, override thebehaviors()method of the component class to which the behavior is being attached

Thebehaviors()method should return a list of behavior configurations


'use strict';

var CandyJs = require('candyjs');
var Controller = CandyJs.Candy.include('candy/web/Controller');

class IndexController extends Controller {
    
    // override
    behaviors() {
    
        return {
            myBehavior: 'app/controllers/index/MyBehavior'
        };
    }
    
    run(req, res) {
        res.end('hello');
    }
    
}

module.exports = IndexController;

To attach a behavior dynamically, call theattachBehavior()method of the component to which the behavior is being attached


'use strict';

var CandyJs = require('candyjs');
var Controller = CandyJs.Candy.include('candy/web/Controller');

class IndexController extends Controller {
    
    constructor(context) {
        super(context);
        
        this.attachBehavior('myBehavior', 'app/controllers/index/MyBehavior');
    }
    
    run(req, res) {
        res.end('hello');
    }
    
}

module.exports = IndexController;

Using behaviors

Once a behavior is attached to a component, its usage is straightforward

For unnecessary performance overheadCandyJsdoes not really perform the injection operation. To use the function of the behavior class, you must manually call theinject()method before calling the function of the behavior class


'use strict';

var CandyJs = require('candyjs');
var Controller = CandyJs.Candy.include('candy/web/Controller');

class IndexController extends Controller {
    
    constructor(context) {
        super(context);
        
        this.attachBehavior('myBehavior', 'app/controllers/index/MyBehavior');
    }
    
    run(req, res) {
        // manually call
        this.inject();
        
        // using behaviors
        this.myFun();
        console.log(this.props1);
        console.log(this.props2);
        
        res.end('hello');
    }
    
}

module.exports = IndexController;

Handling events

The behavior class can handle events that the component triggers only to override the event class'sevents()method


'use strict';

var CandyJs = require('candyjs');
var Behavior = CandyJs.Candy.include('candy/core/Behavior');

class MyBehavior extends Behavior {
    events() {
        return {
            'beforeActionCall': function(){
                console.log('before');
            },
            'afterActionCall': function() {
                console.log('after');
            }
        }
    }
}

module.exports = MyBehavior;

Middleware

The middleware is the first part of the request to process the request and do the filtering and call the next middleware

CandyJstemporarily only provides a middleware for handling static resources

Static resource

CandyJsdefaults to non-processing of static resources that require the use of middleware


var CandyJs = require('candyjs');

var Hook = CandyJs.Candy.include('candy/core/Hook');
var R = CandyJs.Candy.include('candy/midwares/Resource');

Hook.getInstance().addHook(new R(__dirname + '/public').serve());

new CandyJs({

    ...
    
}).listen(8090, function(){
    console.log('listen on 8090');
});

URI & URL

candy/web/URI and candy/web/URLclasses provide methods for uri and url operations

candy/web/URI

  • parseUrl()Used for parse url


var URI = CandyJs.Candy.include('candy/web/URI');

var uri = new URI();

/*
{
    source: 'http://xxx.com:8080/abc?q=1#anchor',
    scheme: 'http',
    user: undefined,
    password: undefined,
    host: 'xxx.com',
    port: '8080',
    path: '/abc',
    query: 'q=1',
    fragment: 'anchor'
}
*/
uri.parseUrl('http://xxx.com:8080/abc?q=1#anchor');

candy/web/URL

  • getReferer()Used to get the referer url
  • getHostInfo()Used to get the host section of URI
  • getCurrent()Used to get the current URL
  • to(url[, params = null])Used to create a url


var URL = CandyJs.Candy.include('candy/web/URL');

var url = new URL(req);

// return scheme://host/index/index
url.to('index/index');

// return scheme://host/index/index?id=1#anchor
url.to('index/index', {id: 1, '#': 'anchor'})

Request and Response

CandyJsprovides classescandy/web/Requestandcandy/web/Responsethat handle requests and responses

HTTP Request

Used to handle http requests

candy/web/Requestclass provides a set of instances and static methods to manipulate the required data

  • static parseUrl(request)Simple resolution url
  • static getClientIp(request)Gets the client IP
  • static getQueryString(request, param)Get the request parameter
  • static getParameter(request, param)Get the POST request parameter
  • static getCookie(request, name)Get cookies
  • getQueryString(param)Instance method get the request parameter
  • getParameter(param)Instance method get the POST request parameter
  • getCookie(name)Instance method get cookies
In the use ofgetParameter()to obtain the POST parameter temporarily need to rely on third-party analysis of the body of the middleware will otherwise back to the null


var Request = CandyJs.Candy.include('candy/web/Request');
var request = new Request(req);
var id = request.getQueryString('id');
...

HTTP Response

Outputs a response message to the client

candy/web/Responseclass provides a set of instances and static methods to manipulate the response data

  • setStatusCode(value[, text])Set the http status code
  • setHeader(name, value)Set the header
  • setContent(content)Set the entity content
  • setCookie(name, value[, options])Set a cookie
  • send([content])Send an HTTP response to the client
  • redirect(url[, statusCode = 302])Page redirection
Use the Response to output data


var Response = CandyJs.Candy.include('candy/web/Response');
var response = new Response(res);
response.setContent('some data from server');
response.send();

Use the Response to redirection


var Response = CandyJs.Candy.include('candy/web/Response');
var response = new Response(res);
response.redirect('http://foo.com');

Assistant class

The assistant class encapsulates some common operations

File Assistant classFileHelper

  • getDirname(dir)
  • normalizePath(path[, directorySeparator = '/'])
  • createDirectory(dir[, mode = 0o777[, callback = null]])
  • createDirectorySync(dir[, mode = 0o777])

String helper classStringHelper

  • nIndexOf(str, find, n)Find the position where a string appears at the Nth occurrence in another string
  • trimChar(str, character)
  • lTrimChar(str, character)
  • rTrimChar(str, character)
  • ucFirst(str)
  • htmlSpecialChars(str[, flag = 0[, doubleEncode = true]])
  • filterTags(str[, allowed = ''])Filter html tags

Time Assistant classTimeHelper

  • format(formats[, timestamp = Date.now()])


var Response = CandyJs.Candy.include('candy/helpers/FileHelper');
var Response = CandyJs.Candy.include('candy/helpers/StringHelper');
var Response = CandyJs.Candy.include('candy/helpers/TimeHelper');

// return /a/c
var path = FileHelper.normalizePath('/a/./b/../c');

// return <script>
var str = StringHelper.htmlSpecialChars('<script>');

// return abcxyz
var strTag = StringHelper.filterTags('<a>abc</a>xyz');

// return xxxx-xx-xx xx:xx:xx 
var time = TimeHelper.format('y-m-d h:i:s');

Alias ​​system

CandyJsprovides a alias system

An alias is a string beginning with an@sign. Each alias corresponds to a real physical path

InCandyJseither create class or load class used alias

System built-in alias

  • @candyPoints to the CandyJs directory
  • @appProject directory
  • @runtimeCache directory
  • @rootWebsite root directory

Custom alias

Users can customize aliases


// register alias
CandyJs.Candy.setPathAlias('@lib', '/home/www/library');

// create /home/www/library/MyClass class instance
var obj = CandyJs.Candy.createObject('lib/MyClass');

RESTful

Because the design is not elegant, RESTful model Redesigned since npm package 2.0.0

The usage before npm package 2.0.0

The methods available in RESTful mode is as follows

  • get(route, handler)
  • post(route, handler)
  • put(route, handler)
  • delete(route, handler)
  • patch(route, handler)
  • head(route, handler)
  • options(route, handler)


// add useRestful configuration to use RESTful
var app = new CandyJs({
    'id': 1,
    'debug': true,
    'appPath': __dirname + '/app',
    
    'useRestful': true
});
app.listen(8090, function(){
    console.log(8090)
});

var Restful = CandyJs.Candy.include('candy/web/Restful');
// get route
Restful.get('/abc/{id:\\d+}', function(req, res, id){
    var Request = CandyJs.Candy.include('candy/web/Request');
    var r = new Request(req);
    
    console.log(r.getQueryString('id'));
    console.log(id);
    
    res.end('api get');
});

Restful.addRoute(['GET', 'POST'], '/def/{id:}', function(req, res, id){
    res.end(id);
});

// use app/api/User class's index method to handler request
Restful.get('/xyz/{id:}', 'app/apis/User@index');

// User definition
'use strict';
class User {
    index(req, res, id) {
        res.end(id);
    }
}
module.exports = User;

The usage after npm package 2.0.0

The methods below changed into instance method

  • get(route, handler)
  • post(route, handler)
  • put(route, handler)
  • delete(route, handler)
  • patch(route, handler)
  • head(route, handler)
  • options(route, handler)


var Rest = require('candyjs/restful');

var rest = new Rest({
    appPath: __dirname + '/app',
    debug: true
});

rest.get('/abc/{id:\\d+}', function(req, res, id){
    res.end(String(id));
});

rest.listen('8090', () => {
    console.log('listen on 8090')
});

Routing issues in RESTful patterns

The routes in RESTful are implemented using regular expressions. It can achieve very flexible routing configuration However, the routing performance is poor relative to MVC. ( Routing in the MVC pattern is not implemented in regular expressions. )

Log

CandyJsprovides the ability to log processing but currently only supports file logs

Use the log

Before using the log, you need to register in entry file


'log': {
    'targets': {
        'file': {
            'class': 'candy/log/file/Target',
            'logPath': __dirname + '/logs'
        },
        'other': {...}
    },
    'flushInterval': 10
}

Log interface

  • error(message)
  • warning(message)
  • info(message)
  • trace(message)
  • flush()


var CandyJs = require('candyjs')
var Logger = CandyJs.Candy.include('candy/log/Logger');

var log = Logger.getLogger();
log.error('This is a error message');
log.flush();  // flush data to disk

Cache

CandyJs provides the ability to cache data processing but currently only supports file caching

Use caching

Before using the cache, you need to register in entry file


'cache': {
    'file': {
        'class': 'candy/cache/file/Target',
        'cachePath': '...'
    }
}

Cache interface

  • setSync(key, value, duration)
  • set(key, value, duration, callback)
  • getSync(key)
  • get(key, callback)
  • deleteSync(key)
  • delete(key, callback)


var CandyJs = require('candyjs');
var Cache = CandyJs.Candy.include('candy/cache/Cache');

var c = Cache.getCache('file');

// 同步
c.setSync('key', 'value');
var data = c.getSync('key');

// 异步
c.set('key2', 'value2', undefined, (err) => {
    c.get('sync', (err, data) => {
        res.end(data);
    });
});