hexin
v0.1.3
Published
Hexin Framework
Downloads
21
Readme
HEXIN Node Framework
This is a node framework that enforces scalability, seperation of concern, and obscuring redundent code while empowering developers with what they need to implement additional features without looking at bloated files. This framework was built with .NET's MVC ideology in mind, and takes ideas from their use of Service & Repository (Repo coming soon) Pattern, Auto-Mapper, Inversion of Control, app_start config.
Get Started
Prerequisite
Setup
$ npm install
Run
$ npm start
NPM Scripts
|npm run <script>
|Description|
|-------------------|-----------|
|start
|Serves your app at localhost:8280
(port by default)|
|build
|Builds the application|
|test
|Test the application using mocha|
|apidoc
|Build apidoc page to public/docs/|
Table of contents
Folder Structure
├── __test__ Mocha & chai tests
| └── controllers
| └── helpers
| └── models
| └── services
├── .vscode Settings & snippets for this project (for VS Code)
├── app Main container for the application
| └── controllers Handles routes
| └── helpers Reusable helper methods
| └── models Mongoose models
| └── repos Data access layers (coming soon)
| └── unitOfWork.js Contains all repos
| └── services Business logic (fascades between controller and models)
├── app_start Holds application startup code
| └── index.js Handles ordering & calling of the application startup code
├── configs Holds all configs
├── locales Locale definitions
├── public folders and files that'll be available in http://{url}/
├── views Holds views using handlebars
└── layouts View's layouts
App
The app folder is where developers will spent majority of their time in.
The Flow
All requests made to the server will flow through the system into the following five layers and in this specific order:
Controllers <-> Services <-> (UnitOfWork <-> Repos) <-> Models
It is very important to remember that a layer can never skip over another layer and directly communicate with another layer. All communication must be to their adjacent layers.
The purpose of this flow is to maintain the Separation-of-Concern between each of them. Each layer has its own duty and will only handle their own tasks. A very good example for this would be if you decide to change the database to another type, you just have to swap the models and update the code for the repo (adaptor for data accessing). For this change, all code updates are contained and you do not have to scour the whole project for all endpoints.
Controllers
Controller is responsible for authorization, DTO mapping, calling service methods, and returning of result/errors
The controller will have two base class to extend from; the ControllerBase and ControllerCrudBase. The ControllerBase holds the essential methods and variables needed. ControllerCrudBase is an extension of ControllerBase and holds all basic CRUD calls for the given Service provided in the constructor. Every controller must extend either ControllerBase or ControllerCrudBase.
Constructor
constructor(app: Object)
- controller is instantiated with an app argument
super(app: Object[, controllerName: string[, service: Object]])
- ControllerBase super
super(app: Object[, controllerName: string[, service: Object[, middlewares]]])
- ControllerCrudBase super
Extendable class
ControllerBase
- documentation in https://github.com/ccau1/hexin-core#controllerbase
ControllerCrudBase
- documentation in https://github.com/ccau1/hexin-core#controllercrudbase
ControllerCrudBase automatically initalizes the following routes:
- [GET]
api/{route}/
- Get All - [GET]
api/{route}/:_id
- Get by _id - [POST]
api/{route}/
- Create - [PUT]
api/{route}/:_id
- Update by _id - [DELETE]
api/{route}/:_id
- Delete by _id
Helper methods
authenticate
- a middleware that verifies whether user is logged in or not
router.post('/', this.authenticate, (req, res, next) {
authorize(...roles: Array<string>)
- a middleware that can be used as authenticate or can be called as function with permitted roles
router.post('/', this.authorize, (req, res, next) {
router.post('/', this.authorize(), (req, res, next) { // (same as above)
router.post('/', this.authorize('user', 'admin'), (req, res, next) {
renderRoutes(router: Object): void
- this is the method where you declare all the routes for this controller. You must override this method in each controller
this.isVerb(verb: string, inVerbList: Array<string>|string): boolean
- Function that returns true if first arg is in second argument. Useful for checking if req.method is equal to one of the verbs listed
this.isVerb(req.method, 'PUT|POST|DELETE')
this.isVerb(req.method, ['PUT', 'POST', 'DELETE'])
Example
'use strict';
const {ControllerCrudBase} = require('hexin-core');
// Service
const AuthService = new require('../services/AuthService');
module.exports = class AuthController extends ControllerCrudBase {
constructor(app) {
const baseMiddlewares = [
(req, res, next) => {
// if method is create, update, or delete, ensure user is authenticated first
if (this.isVerb(req.method, 'PUT|POST|DELETE')) {
this.authenticate(req, res, next);
} else {
next();
}
}
];
super(app, 'auth', AuthService, baseMiddlewares);
}
renderRoutes(router) {
const {authorize} = this;
// Routes definition set here
// [POST] Register
router.post('/register', (req, res, next) => {
co(function* () {
const {m} = req;
const result = yield m.createUser(req.body);
res.send(result);
})
.catch(error => {
next(error);
});
});
}
}
Helpers
Helpers are small reusable functions that'll be applicable in multiple endpoints and could even possibly be transferable to other projects
Models
A model defines a database collection/table structure and its constraints/rules. By default, the model uses mongoose to manage this definition and data-modification methods.
Services
Service is responsible for the system's business logic
The service will have two base class to extend from; the ServiceBase and ServiceCrudBase (corresponds to controller's two base classes). The ServiceBase holds the essential methods and variables needed. The ServiceCrudBase is an extension of the ServiceBase class and holds all basic CRUD calls for the given Model provided in the constructor. Every service must extend either ServiceBase or ServiceCrudBase. It is highly recommended that you use the library HandleError to handle any errors
Constructor
constructor(context: Object)
- services will be instantiated with a context argument (represents router's request object)
super(context: Object, model: Object)
Extendable Class
ServiceBase
- documentation in https://github.com/ccau1/hexin-core#servicebase
ServiceCrudBase
- documentation in https://github.com/ccau1/hexin-core#servicecrudbase
ServiceCrudBase automatically provides the following methods:
getAll(): Array<Object>
- Get AllgetById(_id: number): Object
- Get by _idcreate(obj: Object): Object
- Createupdate(_id: number, obj: Object): Object
- Update by _iddelete(_id: number): Object
- Delete by _id
Context Variables (this)
t(localeKey: string, args: Array<string>): string
- translates localeKey into text defined in current locale
lang
- current locale
context
- context passed from parent
_model
- the model passed from constructor
Helper Methods
validate(obj: Object): boolean
- validates model object and returns true if success or throws error if fails. Used before passing it to the database
sanitize
- modify object to become database ready. Used before passing it to the database
mapper
- a DTO mapper to handle from database to client
mapperReverse
- a DTO mapper to handle from client to database
Example
'use strict';
const {ServiceCrudBase} = require('hexin-core');
const indicative = require('indicative');
const {HandleError} = require('hexin-core/helpers');
// Models
const Todo = new require('../models/Todo');
module.exports = class TodoService extends ServiceCrudBase {
constructor(context_) {
super(context_, Todo);
}
/*
ServiceCrudBase already included the following:
getAll(),
getById(_id),
create(obj),
update(_id, obj),
delete(_id)
*/
* setIsComplete(_id, isComplete) {
const {t} = this;
let todo = yield _model.update({'_id': _id}, {$set: {isComplete}});
if (!todo) {
throw new HandleError({_error: [t('err_reset_token_expired')]}, 422);
}
return todo;
}
}
Repos
(coming soon)
App_Start
The app_start folder contains all the configuration files needed to get the app started. Each of the config files will have three lifecycle methods available for it: preInit()
, init()
, postInit()
'use strict';
const {AppStartConfig} = require('hexin-core');
module.exports = class ControllersConfig extends AppStartConfig {
preInit(appConfig: Object) { }
init(appConfig: Object) { }
postInit(appConfig: Object) { }
File Scaffolding
We use plop to generate scaffolds to maintain consistancy in code and folder structure, as well as speeding up development time
Install
$ npm install -g plop
Usage
View File Scaffolding List
$ plop
Trigger specific command
$ plop {scriptName}
Plop Scripts
|npm run g:<script>
|Description|
|-------------------|-----------|
|model
|Generate Model|
|service
|Generate Service|
|controller
|Generate Controller|
|msc
|Generate Model, Service & Controller|
|app_start
|Generate app_start/{name}Config.js|
Useful Requires and Helpers
const configs = require('../../configs').base;
- Project configs
const {HandleError} = require('hexin-core/helpers');
- Error handling
(More Documentation Coming Soon)
Todos
- [ ] Move /views to /app/views
- [x] Use plop for generating files
- [ ] Enhance User functionalities (reset/change password, upload avatar)
- [ ] Add Unit of Work and Repository Layer
- [ ] Use yeoman to generate this framework